#--------------------------------------------------------------------------- # Defang HTML active-content tags # # NB: In case you think the regexes should be /<[ ]*TAG/, I suggest # you *try* such tags in your browser first... # # Unfortunately the "on*" (e.g. "onload=") syntax is such that we can't # reliably look for /onload="/ - there may be whitespace around the =. # This isn't intended to be a full HTML parser, so we'll err on the side of # safety by defanging everything, even though it may be outside of an HTML # context. # # This keeps getting uglier as more and more holes are discovered. # # This will be folded into a better sanitizer Real Soon Now... # :0 B * 1^1 \<(html|title|body|meta|app|script|object|embed|i?frame|style) * 1^1 =(3d)?[ ]*["'](&{|([a-z]+script|mocha):) { LOG="Defanging active HTML content$SUBJ" :0 fw | perl -p -e ' #\ if (/)) { #\ $poisoned_spec =~ s/^\s+//g; #\ $poisoned_spec =~ s/\s+$//g; #\ next unless $poisoned_spec; #\ $poisoned_spec =~ s/([^\\])\./$1\\./g; #\ $poisoned_spec =~ s/\*/.*/g; #\ $poisoned_spec =~ s/\?/./g; #\ warn " Checking against \"$poisoned_spec\".\n" if $ENV{"VERBOSE"}; #\ if ($filen =~ /^${poisoned_spec}$/i) { #\ $poisoned = 1; #\ warn " Trapped poisoned attachment \"$filen\".\n"; #\ print "X-Content-Security: NOTIFY\n" if $ENV{"SECURITY_NOTIFY"} || $ENV{"SECURITY_NOTIFY_VERBOSE"}; #\ print "X-Content-Security: REPORT: Trapped poisoned attachment \"$filen\"\n" if $ENV{"SECURITY_NOTIFY"} || $ENV{"SECURITY_NOTIFY_VERBOSE"}; #\ print "X-Content-Security: QUARANTINE\n" if $ENV{"SECURITY_QUARANTINE"}; #\ print "\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 "DO NOT attempt to double-click or run the following attachment!\n"; #\ print "Contact your system administrator immediately!\n\n"; #\ print "SUSPICIOUS ATTACHMENT: "; #\ last; #\ } #\ } #\ close(POISONED); #\ } else { #\ warn " WARNING: Unable to open poisoned-executables file \"$specf\".\n"; #\ } #\ } #\ if (length($filen) > 80) { #\ warn " Truncating long filename.\n"; #\ $filen =~ s/^(.{80,}).*$/$1/; #\ } #\ if (!$poisoned && ($filen =~ /\.($ENV{"SAFE_EXTENSIONS"})$/io)) { #\ warn " Attachment filename \"$filen\" considered safe.\n"; #\ } else { #\ warn " Mangling attachment filename.\n"; #\ $filen =~ s/\.([a-z]+)$/.${$}DEFANGED-$1/i; #\ } #\ print "begin $perms $filen\n"; #\ $_ = ""; #\ } #\ ' 2>> $LOGFILE # MIME attachments :0 * 1^0 ^Content-Type[ ]*:.*(application|multipart)/[-a-z]+; * 1^0 ^Content-Disposition[ ]*:.*attachment { LOG="Sanitizing MIME 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:-25} :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.1 $x\$Date: 2001-02-22 20:08:07-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+= 99 if /\000select\s[^\000]*shell\s*\(/i; #\ $score+= 9 if /\000regedit/i; #\ $score+= 9 if /\000SaveNormalPrompt/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: REPORT: Trapped macro-poisoned Microsoft attachment \"$filen\"\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 "DO NOT double-click on the document!\n"; #\ print "Contact your system administrator immediately!\n\n"; #\ } #\ print $lastline; #\ } else { #\ warn " Cannot decode attachment: $! - is mimencode installed?\n"; #\ } #\ } else { #\ warn " Cannot extract attachment: $! - is mktemp installed?\n"; #\ } #\ } #\ if ($inmimehdr || $hdrcnt) { #\ if (/^(\s+\S|(file)?name)/) { #\ s/^\s*/ /; #\ s/^\s*// if $hdrtxt =~ /"[^"]+$/; #\ s/\s*\n$//; #\ $hdrtxt .= $_; #\ $_ = ""; #\ } else { #\ if ($hdrtxt) { #\ $hdrtxt =~ s/([^\\])\\"/\1\\ÿ/g; #\ if ($hdrtxt =~ /`\s*`/) { #\ warn " Fixing double backquotes.\n"; #\ $hdrtxt =~ s/`\s*`/\\"/g; #\ } #\ if ($hdrtxt =~ /^[-\w]+\s*:.*name\s*=\s*"[^"]+$/i) { #\ warn " Fixing missing close quote on filename.\n"; #\ $hdrtxt .= "\""; #\ } #\ while (($junk,$filen) = $hdrtxt =~ /^Content-[-\w]+\s*:[^"]*("[^"]*"[^"]+)*name\s*=\s*([^"\s][^;]+)/i) { #\ warn " Fixing unquoted filename \"$filen\".\n"; #\ $newfilen = $filen; #\ $newfilen =~ s/\"/\\ÿ/g; #\ $filen = quotemeta($filen); #\ $hdrtxt =~ s/name\s*=\s*${filen}/name="$newfilen"/ig; #\ } #\ 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; #\ } #\ if (($filen) = $hdrtxt =~ /^Content-[-\w]+\s*:.*name\s*=\s*"([^"]+)"/i) { #\ warn " MIME attachment filename \"$filen\".\n"; #\ warn " Already poisoned.\n" if $poisoned; #\ if (!$poisoned && ($filen =~ /\.(do[ct]|xl[swt]|p[po]t|rtf)$/i)) { #\ if ($ENV{"DISABLE_MACRO_CHECK"}) { #\ warn " Not macro scanning \"$filen\".\n"; #\ } else { #\ $check_attachment = 1; #\ warn " Macro scanning \"$filen\".\n"; #\ } #\ } #\ if (!$poisoned && ($specf = $ENV{"POISONED_EXECUTABLES"})) { #\ if (open(POISONED,$specf)) { #\ while (chomp($poisoned_spec = )) { #\ $poisoned_spec =~ s/^\s+//g; #\ $poisoned_spec =~ s/\s+$//g; #\ next unless $poisoned_spec; #\ $poisoned_spec =~ s/([^\\])\./$1\\./g; #\ $poisoned_spec =~ s/\*/.*/g; #\ $poisoned_spec =~ s/\?/./g; #\ warn " Checking against \"$poisoned_spec\".\n" if $ENV{"VERBOSE"}; #\ if ($filen =~ /^${poisoned_spec}$/i) { #\ warn " Trapped poisoned attachment \"$filen\".\n"; #\ $poisoned = 1; #\ $check_attachment = 0; #\ print "Content-Type: TEXT/PLAIN;\n"; #\ print "X-Content-Security: NOTIFY\n" if $ENV{"SECURITY_NOTIFY"} || $ENV{"SECURITY_NOTIFY_VERBOSE"}; #\ print "X-Content-Security: REPORT: Trapped poisoned attachment \"$filen\"\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"; #\ last; #\ } #\ } #\ close(POISONED); #\ } else { #\ warn " WARNING: Unable to open poisoned-attachments file \"$specf\".\n"; #\ } #\ } #\ if ($filen =~ /\.($ENV{"SAFE_EXTENSIONS"})$/io) { #\ warn " Attachment filename \"$filen\" considered safe.\n"; #\ } else { #\ warn " Mangling attachment filename \"$filen\".\n"; #\ $newfilen = $filen; #\ $newfilen =~ s/\.([a-z]+)$/.${$}DEFANGED-$1/i; #\ $filen = quotemeta($filen); #\ $hdrtxt =~ s/name\s*=\s*"?${filen}"?/name="$newfilen"/ig; #\ } #\ } #\ 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; #\ } #\ $hdrtxt =~ s/\\ÿ/\\"/g; #\ print $hdrtxt, "\n"; #\ $hdrtxt = ""; #\ } #\ if (/^\S/) { #\ s/\s*\n$//; #\ $hdrtxt = $_; #\ $_ = ""; #\ $hdrcnt++; #\ } else { #\ $hdrcnt = 0; #\ $hdrtxt = ""; #\ } #\ } #\ } else { #\ $poisoned = 0; #\ } #\ } #\ ' 2>> $LOGFILE } :0 HB * ^X-Content-Security: (NOTIFY|QUARANTINE) { :0 * 1^0 SECURITY_NOTIFY ?? [^ ] * 1^0 SECURITY_NOTIFY_VERBOSE ?? [^ ] { # Notify administration and sender of the attack STATUS="STATUS: Message delivered to $TO" STATUS_PUBLIC="STATUS: Message delivered." REPORT="REPORT: No details available." SCORE="REPORT: Not a document, not scanned for macros." :0 * SECURITY_QUARANTINE ?? [^ ] { STATUS="STATUS: Message quarantined in $SECURITY_QUARANTINE, not delivered to recipient." STATUS_PUBLIC="STATUS: Message quarantined, not delivered to recipient." } :0 B * ^\/Macro Scanner score: [0-9]+ { SCORE="REPORT: $MATCH" } :0 HB * ^X-Content-Security: \/REPORT: .* { REPORT="$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 $REPORT;\ echo $SCORE;\ echo $STATUS;\ echo ;\ 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 $REPORT;\ echo $SCORE;\ echo $STATUS;\ echo ;\ echo 'Message:';\ echo ;\ sed -e 's/^/> /' ;\ ) | $SENDMAIL -U $SECURITY_NOTIFY_VERBOSE } :0 H * SECURITY_NOTIFY_SENDER ?? [^ ] * !^FROM_DAEMON * !$ ^X-Loop: EMAIL SECURITY WARNING $HOST { LOG="${NL}NOTIFY SENDER${NL}" :0 h ci | ( \ formail -r \ -I 'From: "Procmail Security daemon" '\ -I "Bcc: $SECURITY_NOTIFY" \ -I "X-Loop: EMAIL SECURITY WARNING $HOST" \ ;\ echo "";\ if [ -f "$SECURITY_NOTIFY_SENDER" -a -s "$SECURITY_NOTIFY_SENDER" -a -r "$SECURITY_NOTIFY_SENDER" ] ;\ then \ echo 'Regarding your message to';\ echo $TO;\ echo ;\ cat $SECURITY_NOTIFY_SENDER; \ else \ echo '*** SECURITY WARNING ***';\ echo 'Our email gateway has detected that your message to';\ echo $TO;\ echo 'may contain hazardous embedded scripting or attachments.';\ echo 'Please notify your system administrator by phone right away.';\ echo 'You should make sure your virus signature list';\ echo 'is up-to-date and then rescan your computer.';\ fi ;\ echo ;\ echo $REPORT;\ echo $SCORE;\ echo $STATUS_PUBLIC;\ echo ;\ echo '--';\ echo 'Message sanitized on' $HOST;\ echo 'See http://www.wolfenet.com/~jhardin/procmail-security.html for details.';\ echo ;\ ) | $SENDMAIL -oi -t } } :0 * SECURITY_QUARANTINE ?? [^ ] { :0 $SECURITY_QUARANTINE :0 e * ! SECURITY_QUARANTINE_OPTIONAL ?? [^ ] { # Argh! Quarantine failed, and not explicitly marked as optional! # Bounce message, and notify administrator LOG="${NL}QUARANTINE FAILED!${NL}" EXITCODE=65 :0 h * SECURITY_NOTIFY ?? [^ ] | ( \ echo "To: $SECURITY_NOTIFY";\ echo 'From: "Procmail Security daemon" ';\ echo 'Subject: SECURITY WARNING - quarantine failed!';\ echo ;\ echo 'Attempt to quarantine the following message in $SECURITY_QUARANTINE failed.';\ echo 'Message has been bounced.';\ echo 'Verify file access permissions:';\ ls -l $SECURITY_QUARANTINE ;\ echo ;\ echo $REPORT;\ echo $SCORE;\ echo ;\ echo 'Headers from message:';\ echo ;\ sed -e 's/^/> /' ;\ ) | $SENDMAIL -U $SECURITY_NOTIFY # zap it, just in case :0 /dev/null } } } #eof