### Copyright (C) 1999 John D. Hardin ### This program is free software; you can redistribute it and/or modify ### it under the terms of the GNU General Public License as published by ### the Free Software Foundation; either version 2 of the License, or ### (at your option) any later version. ### ### This program is distributed in the hope that it will be useful, ### but WITHOUT ANY WARRANTY; without even the implied warranty of ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ### GNU General Public License for more details. ### # # # $Id: html-trap.procmail,v 1.81 1999-04-02 18:35:09-08 jhardin Exp jhardin $ # # Procmail snippet to defang active-content HTML tags to protect those # people foolish enough to read their mail from a web browser or # HTML-enabled mail client. Also mangles the attachment name on executable # attachments to prevent attacks, at the cost of not being able to run # programs from within your mail client - which you shouldn't do anyway. # Also protects against excessively long filenames in attachments, which # can cause nasty things to happen in some clients, and excessively long # MIME headers, which may crash or allow exploits of some clients. # # If you wish to block specific executable or document filenames from # attachments, define $POISONED_EXECUTABLES to point at a filename # containing one filename per line, with no leading spaces. Case is # ignored. # # Site policy for attack messages can be specified within limited bounds. # To redirect poisoned messages to a file, set $SECURITY_QUARANTINE to the # name of the file. To notify administrator(s), set $SECURITY_NOTIFY and/or # $SECURITY_NOTIFY_VERBOSE to a comma-delimited address list. The _VERBOSE # recipients will get the whole message. # # This performs limited scanning of attachments for M$ macros which contain # possibly dangerous code (as opposed to specific strings from specific # variants of specific exploits). To disable this scanning, set # $DISABLE_MACRO_CHECK to anything. To adjust the score an attachment is # considered "poisoned" at, set $POISONED_SCORE. The default is 10, which # should be okay unless you see valid attachments with complex macros. Set # $SCORE_HISTORY to a filename to track the scores on scanned documents. # If you wish to do profiling before implementing poisoning, set # $SCORE_ONLY to anything to scan and possibly record scores but not # poison. This could also be accomplished by setting $POISONED_SCORE to a # very high value. # # This could also be extended fairly easily to allow virus-checking of # attachments, assuming you have a virus-checker that will run under *nix. # # NOTES # Requires perl. # Attachment scanning requires mktemp and mimencode. # # This is a non-delivering filter recipe unless $SECURITY_QUARANTINE is # set. # # This version will error out and requeue the mail if a message with a # TO: list larger than LINEBUF is processed. The mail *WON'T* be lost. I # am working on a better way to do that part. # # INVOCATION # Insert # INCLUDERC=html-trap.procmail # into your .procmailrc at the beginning or end. # # For further details, particularly how to set up site-wide and mail hub # or relay filtering, visit: # ftp://ftp.rubyriver.com/pub/jhardin/antispam/procmail-security.html # LINEBUF=20480 #--------------------------------------------------------------------------- # Grab some info for logging # NL=" " SUBJ="" :0 * ^Subject[ ]*:[ ]+\/.+ { SUBJ=" in \"$MATCH\"" } :0 * ^From[ ]*:[ ]+\/.+ { FROM="$MATCH" SUBJ="$SUBJ from $FROM" } TO=$LOGNAME :0 * LOGNAME ?? ^root$ { # If $LOGNAME is root, we're probably running as a gateway filter: # get the "real" to name(s) out of the message headers. # NOTE: This will run into problems if some bozo sends a mail with a # TO: line that's larger then LINEBUF. The mail won't be lost, though. # It will be improved. I promise. :0 * ^To: +\/.* { TO="$MATCH" } } :0 * ^Message-ID:.*\/<[^>]+> { MSGID="$MATCH" TO="$TO msgid=$MSGID" } SUBJ="$SUBJ to $TO " #--------------------------------------------------------------------------- # Defang HTML active-content tags # # Replace tags in toto rather than trying to parse them. # # NB: In case you think the regexes should be /<[ ]*TAG/, I suggest # you *try* such tags in your browser first... # :0 B * \<(meta|app|script|object|embed|body[ ]+.*onload|frameset) { LOG="Defanging active HTML content$SUBJ" :0 fw | perl -p -e ' #\ if (/)) { #\ warn " Trapped poisoned attachment \"$filen\".\n"; #\ print "X-Content-Security: NOTIFY\n" if $ENV{"SECURITY_NOTIFY"} || $ENV{"SECURITY_NOTIFY_VERBOSE"}; #\ print "X-Content-Security: QUARANTINE\n" if $ENV{"SECURITY_QUARANTINE"}; #\ print "SECURITY WARNING!\n"; #\ print "The mail delivery system has detected that the following \n"; #\ print "attachment may contain hazardous executable code.\n"; #\ print "Contact your system administrator immediately!\n\n"; #\ print "SUSPICIOUS ATTACHMENT: "; #\ } #\ close(POISONED); #\ } #\ warn " Mangling executable filename \"$filen\".\n"; #\ $filen =~ s/\.([a-z]+)$/.${$}DEFANGED-$1/i; #\ print "begin 666 $filen\n"; #\ $_ = ""; #\ } #\ ' 2>> $LOGFILE } } # MIME attachments :0 * 1^0 ^Content-Type[ ]*:.*(application|multipart)/[-a-z]+; * 1^0 ^Content-Disposition[ ]*:.*attachment { LOG="Sanitizing MIME attachment headers$SUBJ" # Due to procmail not unwrapping MIME attachment headers, # (they're in the message body) this perl script has to run against # *every* message with MIME attachments to ensure security. Sorry. # NOTE: I don't use the CPAN MIME module in order to keep this as simple # as possible and to keep it self-contained (i.e. everything is *right here*). # (Attachment scanning breaks this. Which is worse - mimencode or Mime::Base64?) # Make sure $LOGFILE exists so the shell doesn't barf LOGFILE=${LOGFILE:-"/dev/null"} # If you get "Out of memory" errors in your procmail log, try changing to # the following: # :0 fw # | ulimit -d 15000; perl -p -e ' #\ POISONED_SCORE=${POISONED_SCORE:-10} :0 fw | perl -p -e ' #\ $pasthdr = 1 if /^\s*$/; #\ unless ($pasthdr) { #\ if (($type) = /^Content-Type\s*:\s.*(application|multipart)\/[-a-z]+;/i) { #\ $wanthdr = 1; #\ print "X-Security: MIME headers sanitized on ", $ENV{"HOST"}, "\n"; #\ print "\tSee http://www.wolfenet.com/~jhardin/procmail-security.html\n"; #\ print "\tfor details. \$Revision: 1.81 $x\$Date: 1999-04-02 18:35:09-08 $x\n"; #\ if ($type =~ /application/i) { #\ $inmimehdr = 1; #\ } #\ } elsif (/^\S/) { #\ $wanthdr = 0; #\ } #\ if ($wanthdr) { #\ if (($mimeboundary) = /boundary\s*=\s*((".+")|([^"]\S+))/i) { #\ $mimeboundary =~ s/(^"|"$)//g; #\ $rawboundary = $mimeboundary; #\ if ($boundarytoolong = (length($mimeboundary) > 80)) { #\ warn " Truncating long MIME body-part boundary string.\n"; #\ $newboundary = substr($mimeboundary,0,64); #\ $mimeboundary = quotemeta($mimeboundary); #\ s/${mimeboundary}/${newboundary}/; #\ $rawboundary =~ s/${mimeboundary}/${newboundary}/; #\ } else { #\ $mimeboundary = quotemeta($mimeboundary); #\ } #\ } #\ } #\ } #\ if ($mimeboundary || $inmimehdr) { #\ if (/^\s*$/) { #\ $inmimehdr = 0; #\ } elsif (/^--${mimeboundary}(--)?$/o) { #\ $inmimehdr = 1; #\ $check_attachment = 0; #\ s/${mimeboundary}/${newboundary}/ if $boundarytoolong; #\ } elsif (!$inmimehdr && $check_attachment) { #\ $check_attachment = 0; #\ if ($destf = `mktemp /tmp/mailchk.XXXXXX`) { #\ chomp($destf); #\ if (open(DECODE,"|mimencode -u -o $destf")) { #\ do { #\ print $_; #\ print DECODE $_; #\ $_ = <>; #\ $lastline = $_; #\ } until (/^\s*$/ || /^--/); #\ close(DECODE); #\ # Run virus-checker here. #\ open(ATTCH,"< $destf"); #\ $msapp = $score = 0; #\ while () { #\ if ($msapp) { #\ $score+= 99 if /\000VirusProtection/i; #\ $score+= 9 if /\000regedit/i; #\ $score+= 9 if /\000Outlook.Application\000/i; #\ $score+= 4 if /\000ID="{[-0-9A-F]+$/i; #\ $score+= 4 if /\000CreateObject/i; #\ $score+= 4 if /(\000|\004)([a-z0-9_]\.)*(Autoexec|Workbook_(Open|BeforeClose)|Document_(Open|New|Close))/i; #\ $score+= 4 if /(\000|\004)(Logon|AddressLists|AddressEntries|Recipients|Subject|Body|Attachments|Logoff)/i; #\ $score+= 2 if /\000Shell/i; #\ $score+= 2 if /\000Options/i; #\ $score+= 2 if /\000CodeModule/i; #\ $score+= 2 if /\000([a-z]+\.)?Application\000/i; #\ $score+= 2 if /(\000|\004)stdole/i; #\ $score+= 2 if /(\000|\004)NormalTemplate/i; #\ $score+= 1 if /\000ThisWorkbook\000/i; #\ $score+= 1 if /\000PrivateProfileString/i; #\ $score+= 1 if /\000ID="{[-0-9A-F]+}"/i; #\ $score+= 1 if /(\000|\004)(ActiveDocument|ThisDocument)/i; #\ $score+= 1 if /\000\[?HKEY_(CLASSES_ROOT|CURRENT_USER|LOCAL_MACHINE)/; #\ } else { #\ if (/\000(Microsoft (Word Document|Excel Worksheet)|MSWordDoc|Microsoft Excel|Word\.Document\.[0-9]+|Excel\.Sheet\.[0-9]+)\000/) { #\ $msapp = 1; #\ seek(ATTCH,0,0); #\ } #\ } #\ } #\ close(ATTCH); #\ unlink($destf); #\ if ($histfile = $ENV{"SCORE_HISTORY"}) { #\ if (open(HIST,">>$histfile")) { #\ print HIST "score=$score msgid=".$ENV{"MSGID"}." from=".$ENV{"FROM"}."\n"; #\ close HIST; #\ } #\ } #\ $poison_score = $ENV{"POISONED_SCORE"}; #\ $poison_score = 5 if $poison_score < 5; #\ if ($score > $poison_score && !$ENV{"SCORE_ONLY"}) { #\ warn " POSSIBLE MACRO EXPLOIT: Score=$score\n"; #\ print "\n\n--$rawboundary\n"; #\ print "Content-Type: TEXT/PLAIN;\n"; #\ print "X-Content-Security: NOTIFY\n" if $ENV{"SECURITY_NOTIFY"} || $ENV{"SECURITY_NOTIFY_VERBOSE"}; #\ print "X-Content-Security: QUARANTINE\n" if $ENV{"SECURITY_QUARANTINE"}; #\ print "Content-Description: SECURITY WARNING\n\n"; #\ print "SECURITY WARNING!\n"; #\ print "The mail delivery system has detected that the preceding\n"; #\ print "document attachment may contain hazardous macro code.\n"; #\ print "Macro Scanner score: $score\n"; #\ print "Contact your system administrator immediately!\n\n"; #\ } #\ print $lastline; #\ } else { #\ warn " Cannot decode attachment: $!"; #\ } #\ } else { #\ warn " Cannot extract attachment: $!"; #\ } #\ } #\ if ($inmimehdr || $hdrcnt) { #\ if (/^(\s+\S|(file)?name)/) { #\ s/^\s*/ /; #\ s/\s*\n$//; #\ $hdrtxt .= $_; #\ $_ = ""; #\ } else { #\ if ($hdrtxt) { #\ if ($hdrtxt =~ /``/) { #\ warn " Fixing double backquotes.\n"; #\ $hdrtxt =~ s/``/"/g; #\ } #\ if ($hdrtxt =~ /^[-\w]+\s*:.*name\s*=\s*"(\\.|[^"])+$/i) { #\ warn " Fixing missing close quote on filename.\n"; #\ $hdrtxt .= "\""; #\ } #\ while (($filen) = $hdrtxt =~ /^Content-[-\w]+\s*:.*name\s*=\s*([^"\s]\S+)/i) { #\ $filen =~ s/\"/\\"/g; #\ $hdrtxt =~ s/name\s*=\s*[^"\s]\S+/name="$filen"/i; #\ } #\ while (($filen) = $hdrtxt =~ /^Content-[-\w]+\s*:.*name\s*=\s*"((\\.|[^"]){64})(\\.|[^"]){16,}"/i) { #\ warn " Truncating long filename.\n"; #\ $hdrtxt =~ s/name\s*=\s*"(\\.|[^"]){80,}"/name="$filen..."/i; #\ } #\ while (($filen) = $hdrtxt =~ /^Content-[-\w]+\s*:.*name\s*=\s*"((\\.|[^"])+\.(html?|exe|com|cmd|bat|pif|scr|lnk))"/i) { #\ if (!$poisoned && $ENV{"POISONED_EXECUTABLES"} && open(POISONED,$ENV{"POISONED_EXECUTABLES"})) { #\ if (grep(/^\s*$filen/i, )) { #\ warn " Trapped poisoned executable \"$filen\".\n"; #\ $poisoned = 1; #\ print "Content-Type: TEXT/PLAIN;\n"; #\ print "X-Content-Security: NOTIFY\n" if $ENV{"SECURITY_NOTIFY"} || $ENV{"SECURITY_NOTIFY_VERBOSE"}; #\ print "X-Content-Security: QUARANTINE\n" if $ENV{"SECURITY_QUARANTINE"}; #\ print "Content-Description: SECURITY WARNING\n\n"; #\ print "SECURITY WARNING!\n"; #\ print "The mail delivery system has detected that the following\n"; #\ print "attachment may contain hazardous executable code.\n"; #\ print "Contact your system administrator immediately!\n\n"; #\ } #\ close(POISONED); #\ } #\ warn " Mangling executable filename \"$filen\".\n"; #\ $filen =~ s/\.([a-z]+)$/.${$}DEFANGED-$1/i; #\ $hdrtxt =~ s/name\s*=\s*"(\\.|[^"])+"/name="$filen"/i; #\ } #\ if (($filen) = $hdrtxt =~ /^Content-[-\w]+\s*:.*name\s*=\s*"((\\.|[^"])+\.(do[ct]|xl[sw]|rtf))"/i) { #\ if (!$poisoned && $ENV{"POISONED_EXECUTABLES"} && open(POISONED,$ENV{"POISONED_EXECUTABLES"})) { #\ if (grep(/^\s*$filen/i, )) { #\ warn " Trapped poisoned document \"$filen\".\n"; #\ $poisoned = 1; #\ print "Content-Type: TEXT/PLAIN;\n"; #\ print "X-Content-Security: NOTIFY\n" if $ENV{"SECURITY_NOTIFY"} || $ENV{"SECURITY_NOTIFY_VERBOSE"}; #\ print "X-Content-Security: QUARANTINE\n" if $ENV{"SECURITY_QUARANTINE"}; #\ print "Content-Description: SECURITY WARNING\n\n"; #\ print "SECURITY WARNING!\n"; #\ print "The mail delivery system has detected that the following\n"; #\ print "attachment may contain hazardous macro code.\n"; #\ print "Contact your system administrator immediately!\n\n"; #\ } #\ close(POISONED); #\ } #\ $check_attachment = 1 unless $ENV{"DISABLE_MACRO_CHECK"}; #\ } #\ if (($junk) = $hdrtxt =~ /^Content-Type\s*:\s+(.{128}).{100,}$/i) { #\ warn " Truncating long Content-Type header.\n"; #\ $junk =~ s/"/\\"/g; #\ $hdrtxt = "Content-Type: X-BOGUS\/X-BOGUS; originally=\"$junk...\""; #\ } elsif (($junk) = $hdrtxt =~ /^Content-Description\s*:\s+(.{128}).{100,}$/i) { #\ warn " Truncating long Content-Description header.\n"; #\ $hdrtxt = "Content-Description: $junk..."; #\ } elsif (($junk) = $hdrtxt =~ /^Content-[-\w]+\s*:\s+(.{128}).{100,}$/i) { #\ warn " Truncating long MIME header.\n"; #\ $junk =~ s/"/\\"/g; #\ $hdrtxt =~ s/^Content-([-\w]+)\s*:.*$/X-Overflow: Content-$1; originally="$junk..."/i; #\ } #\ #if ($hdrtxt =~ /^Content-Transfer-Encoding\s*:\s+base64/i) { #\ # $check_attachment = 1; #\ #} #\ print $hdrtxt, "\n"; #\ $hdrtxt = ""; #\ } #\ if (/^\S/) { #\ s/\s*\n$//; #\ $hdrtxt = $_; #\ $_ = ""; #\ $hdrcnt++; #\ } else { #\ $hdrcnt = 0; #\ $hdrtxt = ""; #\ } #\ } #\ } else { #\ $poisoned = 0; #\ } #\ } #\ ' 2>> $LOGFILE :0 B * ^X-Content-Security: (NOTIFY|QUARANTINE) { :0 * 1^0 SECURITY_NOTIFY ?? [^ ] * 1^0 SECURITY_NOTIFY_VERBOSE ?? [^ ] { # Notify administration of the attack STATUS="Message delivered to $TO" :0 * SECURITY_QUARANTINE ?? [^ ] { STATUS="Message quarantined in $SECURITY_QUARANTINE, not delivered to recipient." } :0 B * ^\/Macro Scanner score: [0-9]+ { SCORE="$MATCH" } :0 * SECURITY_NOTIFY ?? [^ ] { LOG="${NL}NOTIFY $SECURITY_NOTIFY${NL}" :0 h ci | ( \ echo "To: $SECURITY_NOTIFY";\ echo 'From: "Procmail Security Daemon" ';\ echo 'Subject: SECURITY WARNING - possible email attack';\ echo ;\ echo $STATUS;\ echo $SCORE;\ echo 'Headers from message:';\ echo ;\ sed -e 's/^/> /' ;\ ) | $SENDMAIL -U $SECURITY_NOTIFY } :0 * SECURITY_NOTIFY_VERBOSE ?? [^ ] { LOG="${NL}NOTIFY $SECURITY_NOTIFY_VERBOSE${NL}" :0 hb ci | ( \ echo "To: $SECURITY_NOTIFY_VERBOSE";\ echo 'From: "Procmail Security Daemon" ';\ echo 'Subject: SECURITY WARNING - possible email attack';\ echo ;\ echo $STATUS;\ echo $SCORE;\ echo 'Message:';\ echo ;\ sed -e 's/^/> /' ;\ ) | $SENDMAIL -U $SECURITY_NOTIFY_VERBOSE } } # Note: if delivery to the security quarantine file fails for whatever reason, # the message will still be delivered to the addressee :0 * SECURITY_QUARANTINE ?? [^ ] $SECURITY_QUARANTINE } } #--------------------------------------------------------------------------- # Attempt to detect Melissa Microsoft Word macro worm emails # see http://www.cert.org/advisories/CA-99-04-Melissa-Macro-Virus.html # # This will probably go away if the macro scanning above proves effective, # as scanning for specific strings is not robust. Any minor variation in the # text the worm produces will cause the message to be undetectable. # :0 H * ^Subject:.*important message from { :0 B * Here is that document you asked for * don't show anyone else * ^Content-.*: .*\.(do[ct]|rtf) { LOG='SECURITY-WARNING Possible "Melissa" Microsoft Word macro virus worm message: ' :0 * 1^0 SECURITY_NOTIFY ?? [^ ] * 1^0 SECURITY_NOTIFY_VERBOSE ?? [^ ] { # Notify administration of the attack STATUS="Message delivered to $TO" :0 * SECURITY_QUARANTINE ?? [^ ] { STATUS="Message quarantined in $SECURITY_QUARANTINE, not delivered to recipient." } :0 * SECURITY_NOTIFY ?? [^ ] { LOG="${NL}NOTIFY $SECURITY_NOTIFY${NL}" :0 h ci | ( \ echo "To: $SECURITY_NOTIFY";\ echo 'From: "Procmail Security Daemon" ';\ echo 'Subject: SECURITY WARNING - possible Melissa worm';\ echo ;\ echo 'See http://www.cert.org/advisories/CA-99-04-Melissa-Macro-Virus.html';\ echo ;\ echo 'Headers from message:';\ echo ;\ sed -e 's/^/> /' ;\ echo ;\ echo $STATUS;\ ) | $SENDMAIL -U $SECURITY_NOTIFY } :0 * SECURITY_NOTIFY_VERBOSE ?? [^ ] { LOG="${NL}NOTIFY $SECURITY_NOTIFY_VERBOSE${NL}" :0 hb ci | ( \ echo "To: $SECURITY_NOTIFY_VERBOSE";\ echo 'From: "Procmail Security Daemon" ';\ echo 'Subject: SECURITY WARNING - possible Melissa worm';\ echo ;\ echo 'See http://www.cert.org/advisories/CA-99-04-Melissa-Macro-Virus.html';\ echo ;\ echo 'Message:';\ echo ;\ sed -e 's/^/> /' ;\ echo ;\ echo $STATUS;\ ) | $SENDMAIL -U $SECURITY_NOTIFY_VERBOSE } } # Note: if delivery to the security quarantine file fails for whatever reason, # the message will still be delivered to the addressee :0 * SECURITY_QUARANTINE ?? [^ ] $SECURITY_QUARANTINE } } #eof