A hint: This file contains one or more very long lines, so maybe it is better readable using the pure text view mode that shows the contents as wrapped lines within the browser window.
1 ### Copyright (C) 1997-2004 John D. Hardin 2 ### This program is free software; you can redistribute it and/or modify 3 ### it under the terms of the GNU General Public License as published by 4 ### the Free Software Foundation; either version 2 of the License, or 5 ### (at your option) any later version. 6 ### 7 ### This program is distributed in the hope that it will be useful, 8 ### but WITHOUT ANY WARRANTY; without even the implied warranty of 9 ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 ### GNU General Public License for more details. 11 ### 12 ### Contact the copyright holder for commercial licensing terms 13 ### if you wish to incorporate this code or portions of it into 14 ### non-GPL software. 15 ### 16 # 17 # <jhardin@impsec.org> 18 # $Id: html-trap.procmail,v 1.151 2006-01-20 07:29:24-08 jhardin Exp jhardin $ 19 # 20 # Baroquely complex Procmail and Perl recipe to defang active-content HTML tags 21 # to protect those people foolish enough to read their mail from a web browser 22 # or HTML-enabled mail client. Also mangles the attachment name on executable 23 # attachments to prevent attacks, at the cost of not being able to run 24 # programs from within your mail client - which you shouldn't do anyway. 25 # Also protects against excessively long filenames in attachments, which 26 # can cause nasty things to happen in some clients, and excessively long 27 # MIME headers, which may crash or allow exploits of some clients. 28 # 29 # All of the configuration options discussed below must be set in the 30 # procmail script that calls this filter, before this filter is called. 31 # Please see http://www.impsec.org/email-tools/sanitizer-configuration.html 32 # for complete documentation of all configuration options. 33 # 34 # If you wish to override the default list of executable extensions, 35 # set MANGLE_EXTENSIONS to a regexp (see where it is defined below for 36 # the proper syntax). DO NOT start or end it with a vertical bar or have 37 # two adjacent vertical bars! 38 # 39 # If you wish to block specific executable or document filenames from 40 # attachments, define $POISONED_EXECUTABLES to point at a filename 41 # containing one filename per line, with no leading spaces. Trailing spaces 42 # and comments (beginning with "#") are permitted. 43 # Case is ignored. Wildcards are allowed, using a mishmash of filename 44 # globbing and regular expression syntax. 45 # 46 # Site policy for trapped messages can be specified within limited bounds. 47 # To redirect poisoned messages to a file or directory, set 48 # $SECURITY_QUARANTINE to the name of the file or directory. To notify 49 # administrator(s), set $SECURITY_NOTIFY and/or $SECURITY_NOTIFY_VERBOSE to a 50 # comma-delimited address list. The _VERBOSE recipients will get the whole 51 # message. 52 # 53 # If the trapped message cannot be saved in $SECURITY_QUARANTINE for any 54 # reason, the message will be bounced unless $SECURITY_QUARANTINE_OPTIONAL is 55 # set to any value. 56 # 57 # This performs limited scanning of attachments for M$ macros which contain 58 # possibly dangerous code (as opposed to specific strings from specific 59 # variants of specific exploits). To disable this scanning, set 60 # $DISABLE_MACRO_CHECK to anything. To adjust the score an attachment is 61 # considered "poisoned" at, set $POISONED_SCORE. The default is 25, which 62 # should be okay unless you see valid attachments with complex macros. Set 63 # $SCORE_HISTORY to a filename to track the scores on scanned documents. 64 # If you wish to do profiling before implementing poisoning, set 65 # $SCORE_ONLY to anything to scan and possibly record scores but not 66 # poison. This could also be accomplished by setting $POISONED_SCORE to a 67 # very high value (200?), which would trap attacks while still allowing 68 # profiling. 69 # 70 # This performs scanning of .ZIP archive attachments for filenames. .ZIP 71 # attachments will be handled by the POISON and STRIP lists, and 72 # $ZIPPED_EXECUTABLES lists filespecs that will cause the message to be 73 # quarantined if found in the .ZIP attachment 74 # 75 # If you wish to send a message to the author of a trapped message, set 76 # $SECURITY_NOTIFY_SENDER to any value. To replace the canned message that 77 # is sent to the author of a trapped message, set $SECURITY_NOTIFY_SENDER 78 # to point at a text file for the message body. No variable expansion is 79 # performed on the body of this message. If you want to notify the 80 # sender's postmaster as well, set $SECURITY_NOTIFY_SENDER_POSTMASTER 81 # to any value. If you are not running this on a mail relay, you can use 82 # $SECURITY_NOTIFY_RECIPIENT in the same manner as $SECURITY_NOTIFY_SENDER. 83 # 84 # This could also be extended fairly easily to allow virus-checking of 85 # attachments, assuming you have a virus-checker that will run under *nix. 86 # 87 # NOTES 88 # Requires perl. 89 # Attachment scanning requires the "mktemp" and "mimencode" programs OR 90 # the installation of the CPAN Perl modules MIME::Base64 and File::MkTemp. 91 # Set $USE_CPAN if you want to use CPAN modules. If you can't put those 92 # modules in their system directories (e.g. you're running this on your 93 # ISP's computer), put them somewhere accessible and set $PVT_CPAN to that 94 # directory. 95 # Scanning of ZIP attachments requires the "unzip" program. 96 # 97 # This is a non-delivering filter recipe unless $SECURITY_QUARANTINE is 98 # set. 99 # 100 # INVOCATION 101 # Insert 102 # CONFIG_VARIABLE=some_value 103 # {repeat as needed} 104 # INCLUDERC=html-trap.procmail 105 # into your .procmailrc at the beginning or end. 106 # 107 # For further details, particularly how to set up site-wide and mail hub 108 # or relay filtering, visit: 109 # http://www.impsec.org/email-tools/procmail-security.html 110 # 111 112 # possible bug workaround? 113 LINEBUF=8192 114 115 # Size LINEBUF dynamically to deal with excessively large headers 116 :0 H 117 * 32000^0 118 * 1^1 . 119 { 120 LINEBUF="$=" 121 } 122 123 # override csh and cousins 124 :0 125 * SHELL ?? csh$ 126 { 127 SHELL="/bin/sh" 128 } 129 130 # Make sure $LOGFILE exists so the shells don't barf 131 LOGFILE=${LOGFILE:-"/dev/null"} 132 133 #--------------------------------------------------------------------------- 134 # Grab some info for logging 135 # 136 NL=" 137 " 138 SUBJ="" 139 FROM="unknown" 140 FROMDOM="" 141 REPLY_SUPPRESSED="" 142 143 :0 144 * ^Subject[ ]*:[ ]+\/[^ ].+ 145 { 146 SUBJ=" in \"$MATCH\"" 147 } 148 149 OVERRIDEFORMAIL="Xx:" 150 151 # AOL mail servers look up who is dialled in and 152 # add that user's ID as the X-Apparently-From: header 153 # This is pretty spoof-proof 154 155 :0 156 * ^Received: from .*\.aol\.com .* by .*\.aol\.com 157 * ^X-Apparently-From:.*\/[^ ]+@aol\.com 158 { 159 FROM="$MATCH" 160 OVERRIDEFORMAIL="To: $FROM" 161 162 SUBJ="$SUBJ from $FROM" 163 FROMDOM="aol.com" 164 } 165 166 # otherwise, try Return-Path: (the envelope sender) 167 # which is still subject to spoofing but may be 168 # overlooked 169 170 :0 E 171 * ^Return-Path:.*\/<[^>]+@[^>]+> 172 { 173 FROM="$MATCH" 174 175 # Did a mailing list rewrite the Return-Path header? 176 :0 177 * -1^0 178 * 1^0 ^Precedence: (bulk|junk|list) 179 * 1^0 ^(List-Id|X-Mailing-List): 180 * 9876543210^0 FROM ?? \<owner- 181 * 9876543210^0 FROM ?? \<[^@ >]+-l-admin@ 182 { 183 :0 184 * ^From:.*\/<[^>]+> 185 { 186 FROM="$MATCH" 187 } 188 } 189 190 SUBJ="$SUBJ from $FROM" 191 :0 192 * ^Return-Path:.*<[^@]+@\/[^>]+ 193 { 194 FROMDOM="$MATCH" 195 } 196 } 197 198 # The following runs if Return-Path: header not found 199 # MAKE SURE your MTA puts in a Return-Path: header! 200 :0 E 201 * ^From:.*\/<[^>]+> 202 { 203 FROM="$MATCH" 204 SUBJ="$SUBJ from $FROM" 205 LOG=" WARN: No usable Return-Path: header found in message.${NL}" 206 207 :0 208 * SECURITY_NOTIFY_SENDER ?? [^ ] 209 * ! SECURITY_DISABLE_SMART_REPLY ?? [^ ] 210 { 211 REPLY_SUPPRESSED="NOTICE: No Return-Path: header. Suppressing sender notification.${NL}" 212 LOG=" $REPLY_SUPPRESSED" 213 SECURITY_NOTIFY_SENDER= 214 } 215 } 216 217 TO=<$LOGNAME> 218 219 :0 220 * LOGNAME ?? ^root$ 221 { 222 # If $LOGNAME is root, we're probably running as a gateway filter: 223 # get the "real" to name(s) out of the message headers. 224 :0 225 * ^To: +\/.* 226 { 227 TO="$MATCH" 228 } 229 } 230 231 # try to get the full recipient address from a Received: header 232 # this overrides LOGNAME and the To: header 233 :0 234 * ^Received: .*for \/<?[^> ]+@[^> ]+\.[^> ;]+>? 235 { 236 TO="$MATCH" 237 } 238 239 :0 240 * ^Received: .*for \/<[^> ]+@[^> ]+\.[a-z][a-z][a-z]?[a-z]?[a-z]?[a-z]?> 241 { 242 TO="$MATCH" 243 } 244 245 SUBJ="$SUBJ to $TO" 246 247 :0 248 * ^Message-ID:.*\/<[^>]+> 249 { 250 MSGID="$MATCH" 251 SUBJ="$SUBJ msgid=$MSGID" 252 } 253 254 SUBJ="$SUBJ 255 " 256 257 #--------------------------------------------------------------------------- 258 # Override these in /etc/procmailrc as needed 259 # 260 261 STRIPPED_WARNING=${STRIPPED_WARNING:-" 262 SECURITY NOTICE: 263 264 The mail system has removed a file attachment from this message. 265 The attachment has been discarded. 266 267 Please contact your system administrator for details. 268 269 "} 270 271 POISONED_WARNING=${POISONED_WARNING:-" 272 SECURITY WARNING! 273 274 The mail system has detected that the following 275 attachment may contain hazardous program code, is 276 a suspicious file type, or has a suspicious file name. 277 Do not trust it. Contact your system administrator immediately. 278 279 "} 280 281 MACRO_WARNING=${MACRO_WARNING:-" 282 SECURITY WARNING! 283 284 The mail delivery system has detected that the preceding 285 document attachment appears to contain hazardous macro code. 286 Macro Scanner score: "} 287 288 MACRO_DETAILS=${MACRO_DETAILS:-"Macro Scanner score details: 289 " 290 291 ZIP_MAGIC_WARNING=${ZIP_MAGIC_WARNING:-" 292 SECURITY WARNING! 293 294 The mail system has detected that the preceding attachment 295 claims to be a ZIP archive, but it does not have a valid ZIP 296 archive signature. 297 Do not trust it. Contact your system administrator immediately. 298 299 "} 300 301 ZIPPED_WARNING=${ZIPPED_WARNING:-" 302 SECURITY WARNING! 303 304 The mail system has detected that the preceding ZIP archive 305 attachment contains suspicious files. 306 Do not trust it. Contact your system administrator immediately. 307 308 The suspicious files in the archive are: 309 310 "} 311 312 :0 313 * ! DISABLE_RAR_SCAN ?? [^ ] 314 * ! ? type unrar >/dev/null 2>&1 315 { 316 # unrar not on $PATH 317 LOG=" unrar not found, disabling RAR scan - either install unrar or define DISABLE_RAR_SCAN${NL}" 318 DISABLE_RAR_SCAN=Y 319 } 320 321 322 RAR_MAGIC_WARNING=${RAR_MAGIC_WARNING:-" 323 SECURITY WARNING! 324 325 The mail system has detected that the preceding attachment 326 claims to be a RAR archive, but it does not have a valid RAR 327 archive signature. 328 Do not trust it. Contact your system administrator immediately. 329 330 "} 331 332 RARRED_WARNING=${RARRED_WARNING:-" 333 SECURITY WARNING! 334 335 The mail system has detected that the preceding RAR archive 336 attachment contains suspicious files. 337 Do not trust it. Contact your system administrator immediately. 338 339 The suspicious files in the archive are: 340 341 "} 342 343 BAD_BASE64_WARNING=${BAD_BASE64_WARNING:-" 344 SECURITY WARNING! 345 346 The mail system has detected that the preceding attachment 347 was encoded in a manner intended to bypass security filtering. 348 Do not trust it. Contact your system administrator immediately. 349 350 "} 351 352 BAD_JPEG_WARNING=${BAD_JPEG_WARNING:-" 353 SECURITY WARNING! 354 355 The mail system has detected that the preceding attachment 356 appears to be a JPEG image containing a Microsoft buffer overflow attack. 357 Do not trust it. Contact your system administrator immediately. 358 359 "} 360 361 WMF_WARNING=${WMF_WARNING:-" 362 SECURITY WARNING! 363 364 The mail system has detected that the preceding attachment 365 appears to be a WMF image. It has been blocked for security reasons. 366 Contact your system administrator immediately. 367 368 "} 369 370 TNEF_WARNING=${TNEF_WARNING:-" 371 SECURITY NOTICE: 372 373 The mail system has removed a Microsoft attachment for security reasons. 374 The sender should disable sending Rich Text format in Outlook and 375 disable sending TNEF to the Internet from their Microsoft Exchange gateway. 376 377 See http://support.microsoft.com/support/kb/articles/Q241/5/38.ASP 378 and http://www.microsoft.com/TechNet/exchange/2505ch10.asp 379 for more information. 380 381 "} 382 383 384 # FROM address for notifications 385 SECURITY_LOCAL_POSTMASTER=${SECURITY_LOCAL_POSTMASTER:-"postmaster"} 386 387 # MTA command line options when generating messages 388 # get recipient(s) from command line 389 MTA_FLAGS_CMDLN=${MTA_FLAGS_CMDLN:-"-U"} 390 # get recipient(s) from message headers 391 MTA_FLAGS_HDRS=${MTA_FLAGS_HDRS:-"-oi -t -f$SECURITY_LOCAL_POSTMASTER"} 392 393 # How paranoid to be about stuff embedded in documents 394 # default: very 395 SC_MBD=${SECURITY_OFFICE_EMBED_SCORE:-99} 396 397 398 #--------------------------------------------------------------------------- 399 # trap some excessively long RFC-822 headers 400 # 401 402 :0 403 * ^\/Subject: ..................................................................................................................................................................................................................................................* 404 { 405 LOG=" Trapped excessively long header$SUBJ" 406 STATUS="STATUS: Header truncated." 407 HDR="$MATCH" 408 409 # truncate the header 410 :0 fw h 411 * ^\/Subject: ................................................................................................................................................................................................................................................. 412 | formail -i "$MATCH" 413 414 SECURITY_NOTIFY=${SECURITY_NOTIFY:-"postmaster"} 415 416 :0 417 * ! SECURITY_NONOTIFY_LONGSUBJECT ?? [^ ] 418 * SECURITY_NOTIFY ?? [^ ] 419 * !$ ^X-Loop: EMAIL SECURITY WARNING $HOST $SECRET 420 { 421 LOG="${NL} NOTIFY $SECURITY_NOTIFY${NL}" 422 423 :0 h ci 424 | ( \ 425 echo "To: $SECURITY_NOTIFY";\ 426 echo 'From: "Procmail Security daemon"' "<${SECURITY_LOCAL_POSTMASTER}>";\ 427 echo 'Subject: SECURITY WARNING - possible email attack';\ 428 echo "X-Loop: EMAIL SECURITY WARNING $HOST $SECRET"; \ 429 echo ;\ 430 echo 'Trapped excessively long header:' ;\ 431 echo "$HDR";\ 432 echo ;\ 433 echo "$STATUS";\ 434 echo ;\ 435 echo 'Headers from message:';\ 436 echo ;\ 437 sed -e 's/^/> /' ;\ 438 ) | $SENDMAIL $MTA_FLAGS_CMDLN $SECURITY_NOTIFY 439 } 440 } 441 442 443 :0 444 * \/^((resent-)?(sender|from|(reply-)?to|cc|bcc)|(errors|disposition-notification|apparently)-to|Return-Path): .*<>.*<>.*<>.*<>.*<>.*<>.* 445 { 446 HDR=$MATCH 447 LOG=" Trapped multiple null addresses (possible sendmail attack)" 448 STATUS="STATUS: Header cleaned." 449 450 # truncate the header 451 :0 fw h 452 | sed -e 's/<>.*<>.*<>.*<>.*<>.*<>/<>/g' 453 454 SECURITY_NOTIFY=${SECURITY_NOTIFY:-"postmaster"} 455 456 :0 457 * SECURITY_NOTIFY ?? [^ ] 458 * !$ ^X-Loop: EMAIL SECURITY WARNING $HOST $SECRET 459 { 460 LOG="${NL} NOTIFY $SECURITY_NOTIFY${NL}" 461 462 :0 h ci 463 | ( \ 464 echo "To: $SECURITY_NOTIFY";\ 465 echo 'From: "Procmail Security daemon"' "<${SECURITY_LOCAL_POSTMASTER}>";\ 466 echo 'Subject: SECURITY WARNING - possible email attack';\ 467 echo "X-Loop: EMAIL SECURITY WARNING $HOST $SECRET"; \ 468 echo ;\ 469 echo 'Trapped multiple null addresses (possible sendmail exploit):' ;\ 470 echo "$HDR";\ 471 echo ;\ 472 echo "$STATUS";\ 473 echo ;\ 474 echo 'Headers from message:';\ 475 echo ;\ 476 sed -e 's/^/> /' ;\ 477 ) | $SENDMAIL $MTA_FLAGS_CMDLN $SECURITY_NOTIFY 478 } 479 } 480 481 482 :0 483 * ^\/(Mime-Version|(Resent-)?(Date|Sender|From|Reply-To)|(errors|disposition-notification|apparently)-to|Message-ID|Return-Path|Status|X-Status|X-Keywords|Content-(Class|Type|Disposition|Transfer-Encoding)): ......................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................* 484 { 485 LOG=" Trapped excessively long header$SUBJ" 486 STATUS="STATUS: Message bounced." 487 HDR="$MATCH" 488 489 :0 490 * SECURITY_QUARANTINE ?? [^ ] 491 { 492 STATUS="STATUS: Message quarantined in $SECURITY_QUARANTINE, not delivered to recipient." 493 } 494 495 SECURITY_NOTIFY=${SECURITY_NOTIFY:-"postmaster"} 496 497 :0 498 * SECURITY_NOTIFY ?? [^ ] 499 * !$ ^X-Loop: EMAIL SECURITY WARNING $HOST $SECRET 500 { 501 LOG="${NL} NOTIFY $SECURITY_NOTIFY${NL}" 502 503 :0 h ci 504 | ( \ 505 echo "To: $SECURITY_NOTIFY";\ 506 echo 'From: "Procmail Security daemon"' "<${SECURITY_LOCAL_POSTMASTER}>";\ 507 echo 'Subject: SECURITY WARNING - possible email attack';\ 508 echo "X-Loop: EMAIL SECURITY WARNING $HOST $SECRET"; \ 509 echo ;\ 510 echo 'Trapped excessively long header:' ;\ 511 echo "$HDR";\ 512 echo ;\ 513 echo "$STATUS";\ 514 echo ;\ 515 echo 'Headers from message:';\ 516 echo ;\ 517 sed -e 's/^/> /' ;\ 518 ) | $SENDMAIL $MTA_FLAGS_CMDLN $SECURITY_NOTIFY 519 } 520 521 :0 522 * SECURITY_QUARANTINE ?? [^ ] 523 { 524 :0 :${SECURITY_QUARANTINE_LOCKFILE} 525 $SECURITY_QUARANTINE 526 527 :0 e 528 { 529 # Argh! Quarantine failed! 530 # notify administrator 531 LOG="${NL} ERR: QUARANTINE FAILED!${NL}" 532 533 # bounce it. 534 EXITCODE=65 535 536 :0 h 537 * SECURITY_NOTIFY ?? [^ ] 538 * !$ ^X-Loop: EMAIL SECURITY WARNING $HOST $SECRET 539 | ( \ 540 echo "To: $SECURITY_NOTIFY";\ 541 echo 'From: "Procmail Security daemon"' "<${SECURITY_LOCAL_POSTMASTER}>";\ 542 echo 'Subject: SECURITY WARNING - quarantine failed!';\ 543 echo "X-Loop: EMAIL SECURITY WARNING $HOST $SECRET"; \ 544 echo ;\ 545 echo 'Attempt to quarantine the following message in $SECURITY_QUARANTINE failed.';\ 546 echo 'Message has been bounced.';\ 547 echo 'Verify file access permissions:';\ 548 ls -l $SECURITY_QUARANTINE 2>&1 ;\ 549 echo ;\ 550 echo 'Headers from message:';\ 551 echo ;\ 552 sed -e 's/^/> /' ;\ 553 ) | $SENDMAIL $MTA_FLAGS_CMDLN $SECURITY_NOTIFY 554 555 } 556 } 557 558 # bounce it. 559 EXITCODE=65 560 561 # zap it. 562 :0 563 /dev/null 564 565 } 566 567 #--------------------------------------------------------------------------- 568 # Not all MIME can be sanitized... 569 # 570 :0 571 * ^Content-Type[ ]*:.*multipart/((signed)|(encrypted)); 572 * ! SECURITY_DEFANG_SIGNED ?? [^ ] 573 { 574 LOG=" WARN: Cannot sanitize message due to signing or encryption$SUBJ" 575 } 576 577 #--------------------------------------------------------------------------- 578 # Defang HTML active-content tags 579 # 580 # NB: In case you think the regexes should be /<[ ]*TAG/, I suggest 581 # you *try* such tags in your browser first... 582 # 583 # Unfortunately the "on*" (e.g. "onload=") syntax is such that we can't 584 # reliably look for /onload="/ - there may be whitespace around the =. 585 # This isn't intended to be a full HTML parser, so we'll err on the side of 586 # safety by defanging everything, even though it may be outside of an HTML 587 # context. 588 # 589 # This keeps getting uglier as more and more holes are discovered. 590 # 591 # This will be folded into a better sanitizer Real Soon Now... 592 # 593 594 :0 595 * 9876543210^1 ! ^Content-Type[ ]*:.*multipart/((signed)|(encrypted)); 596 * 9876543210^1 SECURITY_DEFANG_SIGNED ?? [^ ] 597 { 598 #----------- ALL OF THIS IS SKIPPED FOR SIGNED/ENCRYPTED MESSAGES 599 600 # "perl -e" has problems when run as root... 601 DROPPRIVS=YES 602 603 :0 604 * DEBUG_VERBOSE ?? [^ ] 605 { 606 VERBOSE=YES 607 } 608 609 :0 H 610 * [\015][^\012] 611 { 612 # this apparently happens a lot, so stop logging it 613 #LOG="Defanging bare CR in headers$SUBJ" 614 615 :0 fw h 616 | perl -p -e 's/\015/\012/g' 617 } 618 619 :0 B 620 * ! SECURITY_TRUST_HTML ?? [^ ] 621 * 9876543210^1 \<(html|title|body|meta|app|script|object|embed|i?frame|style|img|bgsound|i?layer|link|form|input|table|th|td|xml) 622 * 9876543210^1 =(3d)?[ ]*["'](&{|([a-z]+script|mocha):) 623 { 624 625 LOG="Defanging active HTML content$SUBJ" 626 627 HAVE_UUE= 628 629 :0 B 630 * ^begin[ ]+([0-9]+)?[ ]+[^ ]+ 631 { 632 HAVE_UUE=YES 633 LOG=" UUE content, HTML defang suppression enabled.$NL" 634 } 635 636 :0 fw b 637 | perl -p -e ' #\ 638 unless ($ENV{"HAVE_UUE"} && /^M.{60}$/ ) { #\ 639 if (/ / && /["\047][^"\047\s]*&#x?[1-9][0-9a-f]/i) { #\ 640 while (/["\047][^"\047\s]*&#((4[6-9]|5[0-8]|6[4-9]|[78][0-9]|9[07-9]|1[0-1][0-9]|12[0-2])(?![0-9]))/) { #\ 641 $char = chr($1); #\ 642 s/&#$1;?/$char/g; #\ 643 } #\ 644 while (/["\047][^"\047\s]*&#(x(2[ef]|3[0-9a]|4[0-9a-f]|5[0-9a]|6[1-9a-f]|7[0-9a]))/i) { #\ 645 $char = chr(hex("0$1")); #\ 646 s/&#$1;?/$char/gi; #\ 647 } #\ 648 } #\ 649 if (/ / && /["\047][^"\047\s]*%[2-7][0-9a-f]/i) { #\ 650 while (/["\047][^"\047\s]*%((2[ef]|3[0-9a]|4[0-9a-f]|5[0-9a]|6[1-9a-f]|7[0-9a]))/i) { #\ 651 $char = chr(hex("0x$1")); #\ 652 s/%$1/$char/gi; #\ 653 } #\ 654 } #\ 655 if (/<|%3c/) { #\ 656 s/(<|%3c)(META|APP|SCRIPT|OBJECT|EMBED|FRAME|IFRAME|LAYER|ILAYER|LINK|FORM|INPUT|XML)/$1DEFANGED_$2/gi; #\ 657 unless ($ENV{"SECURITY_TRUST_STYLE_TAGS"}) { #\ 658 s/<STYLE/ <!-- <DEFANGED_STYLE/gi; #\ 659 s/<\/STYLE/ --> <\/DEFANGED_STYLE/gi; #\ 660 s/\sSTYLE\s*=/ DEFANGED_STYLE=/gi; #\ 661 } #\ 662 if ($ENV{"DEFANG_WEBBUGS"}) { #\ 663 s/<IMG/<DEFANGED_IMG/gi; #\ 664 s/<BGSOUND/<DEFANGED_BGSOUND/gi; #\ 665 if (/<(BODY|TABLE|TH|TD)\s/i) { #\ 666 s/\sBACKGROUND\s*=\s*/ DEFANGED_BACKGROUND=/gi; #\ 667 } #\ 668 } #\ 669 s/\sOn(Abort|Blur|Change|Click|DblClick|DragDrop|Error|Focus|KeyDown|KeyPress|KeyUp|Load|MouseDown|MouseMove|MouseOut|MouseOver|MouseUp|Move|Reset|Resize|Select|Submit|Unload|ContextMenu|DragStart)/ DEFANGED_On$1/gi; #\ 670 } #\ 671 s/^\s*On(Abort|Blur|Change|Click|DblClick|DragDrop|Error|Focus|KeyDown|KeyPress|KeyUp|Load|MouseDown|MouseMove|MouseOut|MouseOver|MouseUp|Move|Reset|Resize|Select|Submit|Unload|ContextMenu|DragStart)/DEFANGED_On$1/gi; #\ 672 s/(["\047\075]|url\()([a-z]+script|mocha):/${1}DEFANGED_$2:/gi; #\ 673 s/(["\047\075])&{/${1}DEFANGED_&_{/g; #\ 674 } #\ 675 ' 676 } 677 678 #--------------------------------------------------------------------------- 679 # Mangle HTML and executable attachment filenames enough that they won't 680 # automatically execute, and limit the length of extremely long attachment 681 # filenames and MIME headers to prevent buffer overflows and client 682 # crashes (sigh). Adding ${$} to the mangling inserts a bit of randomness 683 # so that an active-HTML or BO exploit can't just look for an attachment 684 # named EXPLOIT.DEFANGED-EXE to get around the defanging. 685 # 686 # NOTE: the [ ] has a tab embedded in it - DO NOT remove it... 687 # 688 689 :0 690 * ! MANGLE_EXTENSIONS ?? [^ ] 691 { 692 MANGLE_EXTENSIONS='html?|exe|com|cmd|bat|pif|sc[rt]|lnk|dll|ocx|do[ct]|xl[swt]|p[po]t|rtf|vb[se]?|hta|p[lm]|sh[bs]|hlp|chm|eml|ws[cfh]|ad[ep]|jse?|md[abew]|ms[ip]|reg|as[dfx]|c[ip]l|pps|wm[avszdf]|vcf|nws|wsz|\{[-0-9a-f]+\}' 693 } 694 695 696 # UUE attachments 697 :0 B 698 * ^begin[ ]+([0-9]+)?[ ]+[^ ]+ 699 { 700 :0 B 701 * ^begin[ ]+([0-9]+)?[ ]+\/[^ ]....................................................................................................+$ 702 { 703 LOG="Truncating extremely long attachment filename $MATCH$SUBJ" 704 705 :0 fw b 706 | perl -p -e 'if (/^begin\s+[0-9]*\s/i) { #\ 707 ($mode, $filen) = /^begin\s+([0-9]*)\s+(.{64}).*$/i; #\ 708 $mode = "644" unless $mode; #\ 709 s/^.*$/begin $mode $filen.../ if $filen; #\ 710 }' 711 } 712 713 :0 B 714 * $ ^begin[ ]+([0-9]+)?[ ]+.+\.(${MANGLE_EXTENSIONS})[ ]*$ 715 { 716 LOG="Sanitizing executable UUE attachments$SUBJ" 717 718 :0 fw b 719 | perl -p -e ' #\ 720 if ($stripped) { #\ 721 chomp; #\ 722 if (/^end$/i || /^\s*$/) { #\ 723 $stripped = 0; #\ 724 } #\ 725 $_ = ""; next; #\ 726 } #\ 727 if (($junk,$filen) = /^begin\s+([0-9]+\s+)?((\\.|[^"])+\.($ENV{"MANGLE_EXTENSIONS"}|\{[-0-9a-f]+\}))[\.\s]*$/io) { #\ 728 if ($specf = $ENV{"STRIPPED_EXECUTABLES"}) { #\ 729 if (open(STRIPPED,$specf)) { #\ 730 warn " Checking UUE \"$filen\" for stripping.\n"; #\ 731 while (chomp($stp_spec = <STRIPPED>)) { #\ 732 $stp_spec =~ s/^\s+//g; #\ 733 $stp_spec =~ s/\s.*$//g; #\ 734 next unless $stp_spec; #\ 735 $stp_spec =~ s/([^\\])\./$1\\./g; #\ 736 $stp_spec =~ s/\*/.*/g; #\ 737 $stp_spec =~ s/\?\?/?./g; #\ 738 $stp_spec =~ s/([^\(]|^)\?/$1./g; #\ 739 $stp_spec .= "\$" unless $stp_spec =~ /\$/; #\ 740 warn " Checking against \"$stp_spec\"\n" if $ENV{"DEBUG"}; #\ 741 if ($filen =~ /^${stp_spec}/i) { #\ 742 warn " Stripped UUE attachment \"$filen\".\n"; #\ 743 $stripped = 1; #\ 744 print "\n"; #\ 745 print "X-Content-Security: [" . $ENV{"HOST"} . "] REPORT: UUE attachment \"$filen\" stripped\n"; #\ 746 print "\n"; #\ 747 print $ENV{"STRIPPED_WARNING"}; #\ 748 print "Filename: $filen\n\n"; #\ 749 last; #\ 750 } #\ 751 } #\ 752 close(STRIPPED); #\ 753 if ($stripped) { #\ 754 $_ = <>; #\ 755 $_ = ""; next; #\ 756 } #\ 757 } else { #\ 758 warn " ERR: Unable to open stripped-executables file \"$specf\".\n"; #\ 759 } #\ 760 } #\ 761 if ($specf = $ENV{"POISONED_EXECUTABLES"}) { #\ 762 if (open(POISONED,$specf)) { #\ 763 warn " Checking UUE \"$filen\" for poisoning.\n"; #\ 764 while (chomp($psn_spec = <POISONED>)) { #\ 765 $psn_spec =~ s/^\s+//g; #\ 766 $psn_spec =~ s/\s.*$//g; #\ 767 next unless $psn_spec; #\ 768 $psn_spec =~ s/([^\\])\./$1\\./g; #\ 769 $psn_spec =~ s/\*/.*/g; #\ 770 $psn_spec =~ s/\?\?/?./g; #\ 771 $psn_spec =~ s/([^\(]|^)\?/$1./g; #\ 772 $psn_spec .= "\$" unless $psn_spec =~ /\$/; #\ 773 warn " Checking against \"$psn_spec\"\n" if $ENV{"DEBUG"}; #\ 774 if ($filen =~ /^${psn_spec}/i) { #\ 775 warn " Trapped poisoned attachment \"$filen\".\n"; #\ 776 print "X-Content-Security: [", $ENV{"HOST"}, "] NOTIFY\n" if $ENV{"SECURITY_NOTIFY"} || $ENV{"SECURITY_NOTIFY_VERBOSE"}; #\ 777 print "X-Content-Security: [", $ENV{"HOST"}, "] REPORT: Trapped poisoned attachment \"$filen\"\n" if $ENV{"SECURITY_NOTIFY"} || $ENV{"SECURITY_NOTIFY_VERBOSE"}; #\ 778 print "X-Content-Security: [", $ENV{"HOST"}, "] QUARANTINE\n" if $ENV{"SECURITY_QUARANTINE"}; #\ 779 print "\n"; #\ 780 print $ENV{"POISONED_WARNING"}; #\ 781 print "SUSPICIOUS ATTACHMENT: "; #\ 782 last; #\ 783 } #\ 784 } #\ 785 close(POISONED); #\ 786 } else { #\ 787 warn " ERR: Unable to open poisoned-executables file \"$specf\".\n"; #\ 788 } #\ 789 } #\ 790 warn " Mangling executable UUE filename \"$filen\".\n"; #\ 791 $filen =~ s/\.([-a-z0-9{}]+)$/.${$}DEFANGED-$1/i; #\ 792 print "begin 666 $filen\n"; #\ 793 $_ = ""; #\ 794 } #\ 795 ' 2>> $LOGFILE 796 } 797 } 798 799 # MIME attachments and general header sanitizing 800 :0 801 * !$ ^X-Content-Security: \[${HOST}\] (QUARANTINE|DISCARD) 802 * 9876543210^0 ^Content-Type[ ]*:.*(application|multipart)/[^ ]*[ ]*; 803 * 9876543210^0 ^Content-Type[ ]*:.*message/rfc822 804 * 9876543210^0 ^Content-Disposition[ ]*:.*attachment 805 { 806 LOG="Sanitizing MIME & attachments$SUBJ" 807 808 # Due to procmail not unwrapping MIME attachment headers, 809 # (they're in the message body) this perl script has to run against 810 # *every* message with MIME attachments to ensure security. Sorry. 811 812 # If you get "Out of memory" errors in your procmail log, try changing to 813 # the following: 814 # :0 fw 815 # | ulimit -d 15000; perl -p -e ' #\ 816 817 POISONED_SCORE=${POISONED_SCORE:-25} 818 ZIPPED_EXECUTABLES=${ZIPPED_EXECUTABLES:-"$POISONED_EXECUTABLES"} 819 820 :0 fw 821 | perl -p -e ' #\ 822 $pastmsghdr = 1 if /^\s*$/; #\ 823 $XCS = "X-Content-Security: [" . $ENV{"HOST"} . "]" unless $XCS; #\ 824 if ($pastmsghdr) { #\ 825 if (!$mimebdry && $mimebdrs[0]) { #\ 826 warn " Found no MIME boundary in msg attachment.\n" if $ENV{"DEBUG"}; #\ 827 $mimebdry = pop @mimebdrs; #\ 828 $newbdry = pop @newbdrs; #\ 829 $rawbdry = pop @rawbdrs; #\ 830 $bdrytoolong = pop @bdrstoolong; #\ 831 $gotbdry = pop @gotbdrs; #\ 832 $nullbdry = pop @nullbdrs; #\ 833 } #\ 834 $_ = "" if $strip_att && !$gotbdry; #\ 835 } else { #\ 836 if (($type,$format,$junk) = /^Content-Type\s*:\s.*(application|multipart|message)\/(\S+)\s*(;.*)?$/i) { #\ 837 $wanthdr = 1; #\ 838 print "X-Security: message sanitized on ", $ENV{"HOST"}, "\n"; #\ 839 print "\tSee http://www.impsec.org/email-tools/sanitizer-intro.html\n"; #\ 840 print "\tfor details. \$Revision: 1.151 $x\$Date: 2006-01-20 07:29:24-08 $x\n"; #\ 841 print "X-Security: The postmaster has not enabled quarantine of poisoned messages.\n" unless $ENV{"SECURITY_QUARANTINE"}; #\ 842 if ($type =~ /application/i) { #\ 843 $inmimehdr = 1; #\ 844 } elsif ($type =~ /message/i && $format =~ /rfc822/i) { #\ 845 $rcrsmsg = $inmimehdr = 1; #\ 846 } #\ 847 } elsif (/^\S/) { #\ 848 $wanthdr = 0; #\ 849 } #\ 850 if ($wanthdr) { #\ 851 if (($mimebdry) = /boundary\s*=\s*(("")|("[^"]+")|([^"]\S+)|$)/i) { #\ 852 $mimebdry =~ s/(^"|"$)//g; #\ 853 $rawbdry = $mimebdry; #\ 854 $gotbdry = 1; #\ 855 $wanthdr = 0; #\ 856 $bdrytoolong = $nullbdry = 0; #\ 857 if ($bdrytoolong = (length($mimebdry) > 80)) { #\ 858 warn " Truncating long MIME boundary string.\n"; #\ 859 $newbdry = substr($mimebdry,0,64); #\ 860 $mimebdry = quotemeta($mimebdry); #\ 861 s/${mimebdry}/${newbdry}/; #\ 862 $rawbdry =~ s/${mimebdry}/${newbdry}/; #\ 863 } elsif ($nullbdry = (length($mimebdry) < 1)) { #\ 864 warn " Replacing null MIME boundary string.\n"; #\ 865 $newbdry = "==NULL_MIME_BOUNDARY_ATTACK_SANITIZED-${$}=="; #\ 866 s/boundary\s*=\s*(""|$)/boundary="${newbdry}"/i; #\ 867 } else { #\ 868 $newbdry = $mimebdry; #\ 869 $mimebdry = quotemeta($mimebdry); #\ 870 } #\ 871 } #\ 872 } #\ 873 } #\ 874 if ($mimebdry || ($gotbdry && $nullbdry) || $inmimehdr) { #\ 875 if (/^\s*$/) { #\ 876 $inmimehdr = 0; #\ 877 if ($rcrsmsg) { #\ 878 push @mimebdrs, $mimebdry; #\ 879 push @newbdrs, $newbdry; #\ 880 push @rawbdrs, $rawbdry; #\ 881 push @bdrstoolong, $bdrytoolong; #\ 882 push @gotbdrs, $gotbdry; #\ 883 push @nullbdrs, $nullbdry; #\ 884 $mimebdry = $newbdry = ""; #\ 885 $rcrsmsg = $pastmsghdr = $bdrytoolong = $gotbdry = 0; #\ 886 } #\ 887 } elsif (/^--${mimebdry}(--)?$/) { #\ 888 $mend = $1; #\ 889 s/${mimebdry}/${newbdry}/ if $bdrytoolong; #\ 890 s/^--/--${newbdry}${mend}/ if $nullbdry; #\ 891 if ($mend) { #\ 892 if ($mimebdrs[0]) { #\ 893 $mimebdry = pop @mimebdrs; #\ 894 $newbdry = pop @newbdrs; #\ 895 $rawbdry = pop @rawbdrs; #\ 896 $bdrytoolong = pop @bdrstoolong; #\ 897 $gotbdry = pop @gotbdrs; #\ 898 $nullbdry = pop @nullbdrs; #\ 899 } #\ 900 } else { #\ 901 $inmimehdr = 1; #\ 902 $rcrsmsg = $strip_att = $check_att = 0; #\ 903 } #\ 904 } elsif (!$inmimehdr && $strip_att) { #\ 905 $_ = ""; #\ 906 } elsif (!$inmimehdr && $check_att && !$base64) { #\ 907 warn " Not base64, not scanning\n"; #\ 908 $base64 = $check_att = 0; #\ 909 } elsif (!$inmimehdr && $check_att) { #\ 910 $ATTCH = $destf = $rarh = $ziph = ""; #\ 911 $SIG{PIPE} = 'IGNORE'; #\ 912 if ($ENV{"USE_CPAN"}) { #\ 913 push @INC, $ENV{"PVT_CPAN"} if $ENV{"PVT_CPAN"}; #\ 914 eval "use MIME::Base64; use File::MkTemp;"; die $@ if $@; #\ 915 if (($ATTCH,$destf) = mkstempt("mailchk.XXXXXX","/tmp")) { #\ 916 $destf = "/tmp/$destf"; #\ 917 } else { #\ 918 warn " ERR: mkstempt failed\n"; #\ 919 $ATTCH = $destf = ""; #\ 920 } #\ 921 } else { #\ 922 if (!chomp($destf = `mktemp /tmp/mailchk.XXXXXX`)) { #\ 923 warn " ERR: mktemp failed\n"; #\ 924 $ATTCH = $destf = ""; #\ 925 } else { #\ 926 if (open(ATTCH,"|mimencode -u -o $destf")) { #\ 927 $ATTCH = ATTCH; #\ 928 } else { #\ 929 warn " ERR: mimencode failed: $@\n"; #\ 930 unlink($destf); #\ 931 $ATTCH = $destf = ""; #\ 932 } #\ 933 } #\ 934 } #\ 935 if ($ATTCH && $destf) { #\ 936 warn " Decoding to \"$destf\"\n"; #\ 937 do { #\ 938 print $_; #\ 939 if (length($_) > 3) { #\ 940 if ($ENV{"USE_CPAN"}) { #\ 941 $de = decode_base64($_); #\ 942 print $ATTCH $de || die $@; #\ 943 $rarh = $ziph = $de unless $ziph; #\ 944 } else { #\ 945 print $ATTCH $_ || die $@; #\ 946 } #\ 947 } else { #\ 948 $rarh = $ziph = "XXX" if length($_) > 1; #\ 949 } #\ 950 $_ = <>; #\ 951 $lastline = $_; #\ 952 } until (/^--${mimebdry}(--)?$/ || ($mimebdrs[0] && /^--${mimebdrs[0]}(--)?$/) || !$_ ); #\ 953 if (close($ATTCH)) { #\ 954 # Run virus-checker against $destf here. #\ 955 } else { #\ 956 warn " ERR: decode failed! mimencode?\n"; #\ 957 $check_att = 0; #\ 958 } #\ 959 if ($check_att == 3) { #\ 960 warn " Scanning image\n"; #\ 961 $wmf = $jpegbo = 0; #\ 962 if ($ENV{"SECURITY_POISON_WMF"} && (`/usr/bin/od -N 4 -t x $destf` =~ /9ac6cdd7/i)) { #\ 963 $wmf = 1; #\ 964 warn " Poisoned WMF\n"; #\ 965 } elsif (open(JPEGI,"rdjpgcom $destf 2>&1 |")) { #\ 966 while (<JPEGI>) { #\ 967 warn "djpeg: $_" if $ENV{"DEBUG_VERBOSE"}; #\ 968 if (/Erroneous JPEG marker length/i) { #\ 969 $jpegbo = $_; last; #\ 970 } #\ 971 } #\ 972 close(JPEGI); #\ 973 } #\ 974 if ($jpegbo || $wmf) { #\ 975 print "\n\n--$newbdry\n"; #\ 976 print "Content-Type: TEXT/PLAIN;\n"; #\ 977 print "Content-Description: SECURITY WARNING\n"; #\ 978 print "$XCS NOTIFY\n" if $ENV{"SECURITY_NOTIFY"} || $ENV{"SECURITY_NOTIFY_VERBOSE"}; #\ 979 print "$XCS QUARANTINE\n" if $ENV{"SECURITY_QUARANTINE"}; #\ 980 } #\ 981 if ($jpegbo) { #\ 982 warn " JPEG BO?\n"; #\ 983 print "$XCS REPORT: Trapped possible JPEG attack: $jpegbo" if $ENV{"SECURITY_NOTIFY"} || $ENV{"SECURITY_NOTIFY_VERBOSE"}; #\ 984 print "\n"; #\ 985 print $ENV{"BAD_JPEG_WARNING"}; #\ 986 } #\ 987 if ($wmf) { #\ 988 print "$XCS REPORT: Trapped possible WMF attack." if $ENV{"SECURITY_NOTIFY"} || $ENV{"SECURITY_NOTIFY_VERBOSE"}; #\ 989 print "\n"; #\ 990 print $ENV{"WMF_WARNING"}; #\ 991 } #\ 992 } elsif (($check_att == 2 || $check_att == 4) && ($specf = $ENV{"ZIPPED_EXECUTABLES"})) { #\ 993 if ($check_att == 2) { #\ 994 $rarh = "Rar"; #\ 995 warn " Scanning ZIP\n" if $ENV{"DEBUG"}; #\ 996 unless ($ENV{"USE_CPAN"} || $ziph) { #\ 997 if (open(ATTCH,"<$destf")) { #\ 998 $ziph = <ATTCH>; #\ 999 close(ATTCH); #\ 1000 } else { #\ 1001 warn " ERR: decode failed\n"; #\ 1002 $ziph = "PK\003\004"; #\ 1003 } #\ 1004 } #\ 1005 } elsif ($check_att == 4) { #\ 1006 $ziph = "PK\003\004"; #\ 1007 warn " Scanning RAR\n" if $ENV{"DEBUG"}; #\ 1008 unless ($rarh) { #\ 1009 if (open(ATTCH,"<$destf")) { #\ 1010 $rarh = <ATTCH>; #\ 1011 close(ATTCH); #\ 1012 } else { #\ 1013 warn " ERR: decode failed\n"; #\ 1014 $rarh = "Rar"; #\ 1015 } #\ 1016 } #\ 1017 } #\ 1018 if ($ziph !~ /^(?:PK00)?PK\003\004/ || $rarh !~ /^Rar/) { #\ 1019 if ($ziph eq "XXX") { #\ 1020 warn " Hostile BASE64\n"; #\ 1021 } else { #\ 1022 warn " ZIP/RAR magic not found\n"; #\ 1023 } #\ 1024 print "\n\n--$newbdry\n"; #\ 1025 print "Content-Type: TEXT/PLAIN;\n"; #\ 1026 print "$XCS NOTIFY\n" if $ENV{"SECURITY_NOTIFY"} || $ENV{"SECURITY_NOTIFY_VERBOSE"}; #\ 1027 if ($ziph eq "XXX") { #\ 1028 print "$XCS REPORT: Trapped ZIP/RAR file \"$zfilen\" with hostile base64 encoding\n" if $ENV{"SECURITY_NOTIFY"} || $ENV{"SECURITY_NOTIFY_VERBOSE"}; #\ 1029 } else { #\ 1030 print "$XCS REPORT: Trapped bogus ZIP/RAR file \"$zfilen\"\n" if $ENV{"SECURITY_NOTIFY"} || $ENV{"SECURITY_NOTIFY_VERBOSE"}; #\ 1031 } #\ 1032 print "$XCS QUARANTINE\n" if $ENV{"SECURITY_QUARANTINE"}; #\ 1033 print "Content-Description: SECURITY WARNING\n\n"; #\ 1034 if ($ziph eq "XXX") { #\ 1035 print $ENV{"BAD_BASE64_WARNING"}; #\ 1036 } else { #\ 1037 if ($check_att == 2) { #\ 1038 print $ENV{"ZIP_MAGIC_WARNING"}; #\ 1039 } elsif ($check_att == 4) { #\ 1040 print $ENV{"RAR_MAGIC_WARNING"}; #\ 1041 } #\ 1042 } #\ 1043 } elsif (open(ZIPPOL,$specf)) { #\ 1044 %zipf = (); #\ 1045 if ($check_att == 2) { #\ 1046 if (open(ZIPL,"unzip -l $destf |")) { #\ 1047 while (<ZIPL>) { #\ 1048 if (($filen) = /^\s+\d+\s+\S+\s+\S+\s+(.+)$/) { #\ 1049 warn "$filen\n" if $ENV{"DEBUG"}; #\ 1050 $zipf{$filen} = 1; #\ 1051 } #\ 1052 } #\ 1053 warn " ERR: scan failed! unzip?\n" unless close(ZIPL); #\ 1054 if (open(ZIPL,"unzip -t -P _virus_$$ $destf 2>&1 |")) { #\ 1055 while (<ZIPL>) { #\ 1056 if (($junk,$filen) = /(skipp|test)ing:\s+(\S.+\.\w+)\W*\s+($|incorrect)/i) { #\ 1057 $zipf{$filen} = 1; #\ 1058 } #\ 1059 } #\ 1060 close(ZIPL); #\ 1061 } #\ 1062 } #\ 1063 } elsif ($check_att == 4) { #\ 1064 if (open(RARL,"unrar v $destf |")) { #\ 1065 while (<RARL>) { #\ 1066 if (($filen) = /^[\s*](?:[^\/]+\/)*(\S.*\..+)$/) { #\ 1067 warn "$filen\n" if $ENV{"DEBUG"}; #\ 1068 $zipf{$filen} = 1; #\ 1069 } #\ 1070 } #\ 1071 warn " ERR: scan failed! unrar?\n" unless close(RARL); #\ 1072 } #\ 1073 } #\ 1074 $filel = ""; #\ 1075 foreach $filen (keys %zipf) { #\ 1076 $fn = $filen; $fn =~ s/\s{10,}/ (many spaces) /; #\ 1077 warn " Checking archived \"$fn\"\n"; #\ 1078 seek(ZIPPOL,0,0); #\ 1079 while (chomp($pol_spec = <ZIPPOL>)) { #\ 1080 $pol_spec =~ s/^\s+//g; #\ 1081 $pol_spec =~ s/\s.*$//g; #\ 1082 next unless $pol_spec; #\ 1083 $pol_spec =~ s/([^\\])\./$1\\./g; #\ 1084 $pol_spec =~ s/\*/.*/g; #\ 1085 $pol_spec =~ s/\?\?/?./g; #\ 1086 $pol_spec =~ s/([^\(]|^)\?/$1./g; #\ 1087 warn " Checking against \"$pol_spec\"\n" if $ENV{"DEBUG"}; #\ 1088 if ($filen =~ /^${pol_spec}$/i) { #\ 1089 warn " Trapped archived \"$fn\".\n"; #\ 1090 if (!$poisoned) { #\ 1091 print "\n\n--$newbdry\n"; #\ 1092 print "Content-Type: TEXT/PLAIN;\n"; #\ 1093 print "$XCS NOTIFY\n" if $ENV{"SECURITY_NOTIFY"} || $ENV{"SECURITY_NOTIFY_VERBOSE"}; #\ 1094 print "$XCS REPORT: Scanned attachment \"$zfilen\"\n$XCS REPORT: Trapped suspicious archived files:\n" if $ENV{"SECURITY_NOTIFY"} || $ENV{"SECURITY_NOTIFY_VERBOSE"}; #\ 1095 $poisoned = 1; #\ 1096 } #\ 1097 print "$XCS REPORT: $fn\n" if $ENV{"SECURITY_NOTIFY"} || $ENV{"SECURITY_NOTIFY_VERBOSE"}; #\ 1098 $filel .= " $fn\n"; #\ 1099 last; #\ 1100 } #\ 1101 } #\ 1102 } #\ 1103 if ($poisoned) { #\ 1104 print "$XCS QUARANTINE\n" if $ENV{"SECURITY_QUARANTINE"}; #\ 1105 print "Content-Description: SECURITY WARNING\n\n"; #\ 1106 if ($check_att == 2) { #\ 1107 print $ENV{"ZIPPED_WARNING"}; #\ 1108 } elsif ($check_att == 4) { #\ 1109 print $ENV{"RARRED_WARNING"}; #\ 1110 } #\ 1111 print "$filel\n"; #\ 1112 } #\ 1113 } else { #\ 1114 warn " ERR: Unable to open ZIP/RAR list \"$specf\".\n"; #\ 1115 } #\ 1116 } #\ 1117 unlink($destf); #\ 1118 if ($lastline =~ /^--${mimebdry}(--)?$/) { #\ 1119 $mend = $1; #\ 1120 $lastline =~ s/${mimebdry}/${newbdry}/ if $bdrytoolong; #\ 1121 s/^--/--${newbdry}${mend}/ if $nullbdry; #\ 1122 if ($mend) { #\ 1123 if ($mimebdrs[0]) { #\ 1124 $mimebdry = pop @mimebdrs; #\ 1125 $newbdry = pop @newbdrs; #\ 1126 $rawbdry = pop @rawbdrs; #\ 1127 $bdrytoolong = pop @bdrstoolong; #\ 1128 $gotbdry = pop @gotbdrs; #\ 1129 $nullbdry = pop @nullbdrs; #\ 1130 } #\ 1131 } else { #\ 1132 $inmimehdr = 1; #\ 1133 } #\ 1134 } #\ 1135 print $lastline; $_ = ""; #\ 1136 } #\ 1137 $check_att = 0; #\ 1138 } #\ 1139 if ($inmimehdr || $hdrcnt) { #\ 1140 if (/^(\s+\S|(file)?name)/) { #\ 1141 s/^\s*/ /; #\ 1142 s/^\s*// if $hdrtxt =~ /"[^"]*[^;]$/; #\ 1143 s/\s*\n$//; #\ 1144 $hdrtxt .= $_; #\ 1145 $_ = ""; #\ 1146 } else { #\ 1147 if ($hdrtxt) { #\ 1148 $hdrtxt =~ s/([^\\])\\"/\1\\ÿ/g; #\ 1149 if ($hdrtxt =~ /`\s*`/) { #\ 1150 warn " Fixing double backquotes.\n"; #\ 1151 $hdrtxt =~ s/`\s*`/\\"/g; #\ 1152 } #\ 1153 if ($hdrtxt =~ /^[-\w]+\s*:.*name\s*=\s*"[^"]+$/i) { #\ 1154 warn " Fixing missing close quote on filename.\n"; #\ 1155 $hdrtxt .= "\""; #\ 1156 } #\ 1157 while (($hdr, $val) = $hdrtxt =~ /^([-\w]+)\s*:.*\s(\S+)\s*=\s*""/i) { #\ 1158 warn " Null $val in $hdr header.\n"; #\ 1159 $sval = quotemeta($val); #\ 1160 $hdrtxt =~ s/\s$sval\s*=\s*""/ X-$val="{null value sanitized}"/; #\ 1161 } #\ 1162 unless ($ENV{"SECURITY_DISABLE_OUTLOOK_HACKS"}) { #\ 1163 while (($hdr,$filen) = $hdrtxt =~ /^(Content-Description)\s*:\s*text\s+from\s+file\s+\047([^\047]+)\047/i) { #\ 1164 warn " Fixing file name \"$filen\" in ${hdr}:\n"; #\ 1165 $newfilen = $filen; $filen = quotemeta($filen); #\ 1166 $hdrtxt =~ s/\s+\047${filen}\047/, filename="${newfilen}"/ig; #\ 1167 } #\ 1168 } #\ 1169 while (($junk,$filen,$junk) = $hdrtxt =~ /^Content-[-\w]+\s*:[^"]*("[^"]*"[^"]+)*name\s*=\s*([^"\s]([^;]|;(?!\s))+)/i) { #\ 1170 warn " Fixing unquoted filename \"$filen\".\n"; #\ 1171 $newfilen = $filen; $filen = quotemeta($filen); #\ 1172 if ($newfilen =~ /\.[a-z0-9]+"[a-z0-9"]+[\.\s]*$/i) { #\ 1173 warn " Defanging quotes-in-extension attack.\n"; #\ 1174 while ($newfilen =~ /\.[a-z0-9]+"[a-z0-9"]+[\.\s]*$/i) { #\ 1175 $newfilen =~ s/\.([a-z0-9]+)"([a-z0-9"]+)[\.\s]*$/.$1$2/i; #\ 1176 } #\ 1177 } #\ 1178 $newfilen =~ s/\"/\\"/g; #\ 1179 if ($newfilen =~ /\([^)]*\)/) { #\ 1180 warn " Removing embedded RFC822 comments.\n"; #\ 1181 $newfilen =~ s/\([^)]*\)//g; #\ 1182 } #\ 1183 $hdrtxt =~ s/name\s*=\s*${filen}/name="$newfilen"/ig; #\ 1184 } #\ 1185 while (($filen) = $hdrtxt =~ /^Content-[-\w]+\s*:.*name\s*=\s*"(=\?[^"]+\?Q\?[^"]+=(2e|3[0-9]|[46][1-9a-f]|[57][0-9a])[^"]+\?=)"/i) { #\ 1186 warn " Fixing encoded plain characters in \"$filen\".\n"; #\ 1187 $newfilen = $filen; $filen = quotemeta($filen); #\ 1188 while ($newfilen =~ /=(2e|3[0-9]|[46][1-9a-f]|[57][0-9a])/i) { #\ 1189 $char = chr(hex("0x$1")); #\ 1190 $newfilen =~ s/=$1/$char/gi; #\ 1191 } #\ 1192 $hdrtxt =~ s/name\s*=\s*"${filen}"/name="$newfilen"/ig; #\ 1193 } #\ 1194 while (($filen) = $hdrtxt =~ /^Content-[-\w]+\s*:.*name\s*=\s*"([^"]+)[\.\s]+"/i) { #\ 1195 warn " Fixing trailing spaces/periods in filename.\n"; #\ 1196 $newfilen = $filen; $filen = quotemeta($filen); #\ 1197 $hdrtxt =~ s/name\s*=\s*"${filen}[\.\s]+"/name="$newfilen"/ig; #\ 1198 } #\ 1199 while (($filen) = $hdrtxt =~ /^Content-[-\w]+\s*:.*name\s*=\s*"([^"]{128,})"/i) { #\ 1200 warn " Shortening long filename \"$filen\".\n"; #\ 1201 $filen =~ s/\s+/ /g; #\ 1202 substr ($filen,64,32) = "..." while (length($filen) > 120); #\ 1203 $hdrtxt =~ s/name\s*=\s*"[^"]{120,}"/name="$filen"/i; #\ 1204 $mangle_mime_type = 1; #\ 1205 warn " Filename now \"$filen\".\n"; #\ 1206 } #\ 1207 if (($mtype) = $hdrtxt =~ /^Content-Type:\s+([a-z0-9-_]+\/[a-z0-9-_]+)/i) { #\ 1208 warn " MIME body part type \"$mtype\".\n" if $ENV{"DEBUG"}; #\ 1209 unless ($mtype =~ /^(multipart|text|message)\//i) { #\ 1210 unless ($hdrtxt =~ /name\s*=\s*"/i) { #\ 1211 $dfrhdr .= "$hdrtxt\n"; $hdrtxt = ""; #\ 1212 } #\ 1213 } #\ 1214 if ($mtype =~ /^application\/x-ms-?download/i && ! ($ENV{"SECURITY_TRUST_MS_DOWNLOAD"} || $poisoned)) { #\ 1215 warn " Trapped poisoned $mtype\n"; #\ 1216 $poisoned = 1; $check_att = 0; #\ 1217 print "Content-Type: TEXT/PLAIN;\n"; #\ 1218 print "$XCS NOTIFY\n" if $ENV{"SECURITY_NOTIFY"} || $ENV{"SECURITY_NOTIFY_VERBOSE"}; #\ 1219 print "$XCS REPORT: Trapped poisoned $mtype attachment\n" if $ENV{"SECURITY_NOTIFY"} || $ENV{"SECURITY_NOTIFY_VERBOSE"}; #\ 1220 print "$XCS QUARANTINE\n" if $ENV{"SECURITY_QUARANTINE"}; #\ 1221 print "Content-Description: SECURITY WARNING\n\n"; #\ 1222 print $ENV{"POISONED_WARNING"}; #\ 1223 print "Scanner score: 0 (poisoned by MIME type, scan skipped)\n\n"; #\ 1224 } #\ 1225 if ($mtype =~ /^application\/x(-zip)?-compress(ed)?/i) { #\ 1226 $check_att = 2 unless $ENV{"DISABLE_ZIP_SCAN"} || $poisoned; #\ 1227 } #\ 1228 if ($mtype =~ /^image\//i) { #\ 1229 $check_att = 3 unless $ENV{"DISABLE_JPEG_SCAN"} || $poisoned; #\ 1230 } #\ 1231 } #\ 1232 if ($hdrtxt =~ /^Content-Transfer-Encoding\s*:/i) { #\ 1233 $base64 = ($hdrtxt =~ /:\s+base64/i); #\ 1234 $dfrhdr .= "$hdrtxt\n"; $hdrtxt = ""; #\ 1235 } #\ 1236 if ($hdrtxt =~ /^Content-[-\w]+\s*:.*name\s*=\s*"([^"]*\.(p?jp[eg]+|bmp|gif|png|wmf)(\?=)?)"/i) { #\ 1237 $check_att = 3 unless $ENV{"DISABLE_JPEG_SCAN"} || $poisoned; #\ 1238 } #\ 1239 if (($filen) = $hdrtxt =~ /^Content-[-\w]+\s*:.*name\s*=\s*"([^"]*\.(do[ct]|xl[swt]|p[po]t|rtf|pps|zip|rar)(\?=)?)"/i) { #\ 1240 $fn = $filen; $fn =~ s/\s{10,}/ (many spaces) /; #\ 1241 $stripped = 0; #\ 1242 if ($filen =~ /\.rar(\?=)?$/i) { #\ 1243 $typ = "RAR archive"; #\ 1244 $check_att = 4 unless $ENV{"DISABLE_RAR_SCAN"} || $poisoned; #\ 1245 $zfilen = $filen; #\ 1246 } elsif ($filen =~ /\.zip(\?=)?$/i) { #\ 1247 $typ = "ZIP archive"; #\ 1248 $check_att = 2 unless $ENV{"DISABLE_ZIP_SCAN"} || $poisoned; #\ 1249 $zfilen = $filen; #\ 1250 } else { #\ 1251 $typ = "Office document"; #\ 1252 $check_att = 1 unless $ENV{"DISABLE_MACRO_CHECK"} || $poisoned; #\ 1253 } #\ 1254 if (!$poisoned && ($specf = $ENV{"STRIPPED_EXECUTABLES"})) { #\ 1255 if (open(STRIPPED,$specf)) { #\ 1256 warn " Checking $typ \"$fn\" for stripping.\n"; #\ 1257 while (chomp($stp_spec = <STRIPPED>)) { #\ 1258 $stp_spec =~ s/^\s+//g; #\ 1259 $stp_spec =~ s/\s.*$//g; #\ 1260 next unless $stp_spec; #\ 1261 $stp_spec =~ s/([^\\])\./$1\\./g; #\ 1262 $stp_spec =~ s/\*/.*/g; #\ 1263 $stp_spec =~ s/\?\?/?./g; #\ 1264 $stp_spec =~ s/([^\(]|^)\?/$1./g; #\ 1265 $stp_spec .= "(\\?=)?\$" unless $stp_spec =~ /\$/; #\ 1266 warn " Checking against \"$stp_spec\"\n" if $ENV{"DEBUG"}; #\ 1267 if ($filen =~ /^${stp_spec}/i) { #\ 1268 warn " Stripped $typ \"$fn\".\n"; #\ 1269 print "Content-Type: TEXT/PLAIN;\n"; #\ 1270 print "$XCS REPORT: $typ attachment \"$fn\" stripped\n"; #\ 1271 print "Content-Description: SECURITY NOTICE\n\n"; #\ 1272 print $ENV{"STRIPPED_WARNING"}; #\ 1273 print "Filename: $fn\n\n"; #\ 1274 print "More headers follow:\n\n" unless $pastmsghdr; #\ 1275 $_ = $dfrhdr = $hdrtxt = ""; #\ 1276 $stripped = $strip_att = 1; #\ 1277 $check_att = $inmimehdr = 0; #\ 1278 last; #\ 1279 } #\ 1280 } #\ 1281 close(STRIPPED); #\ 1282 } else { #\ 1283 warn " ERR: Unable to open strip list \"$specf\".\n"; #\ 1284 } #\ 1285 } #\ 1286 if (!$poisoned && !$stripped && ($specf = $ENV{"POISONED_EXECUTABLES"})) { #\ 1287 if (open(POISONED,$specf)) { #\ 1288 warn " Checking $typ \"$fn\" for poisoning.\n"; #\ 1289 while (chomp($psn_spec = <POISONED>)) { #\ 1290 $psn_spec =~ s/^\s+//g; #\ 1291 $psn_spec =~ s/\s.*$//g; #\ 1292 next unless $psn_spec; #\ 1293 $psn_spec =~ s/([^\\])\./$1\\./g; #\ 1294 $psn_spec =~ s/\*/.*/g; #\ 1295 $psn_spec =~ s/\?\?/?./g; #\ 1296 $psn_spec =~ s/([^\(]|^)\?/$1./g; #\ 1297 $psn_spec .= "(\\?=)?\$" unless $psn_spec =~ /\$/; #\ 1298 warn " Checking against \"$psn_spec\"\n" if $ENV{"DEBUG"}; #\ 1299 if ($filen =~ /^${psn_spec}/i) { #\ 1300 warn " Trapped poisoned $typ \"$fn\".\n"; #\ 1301 $poisoned = 1; $check_att = 0; #\ 1302 print "Content-Type: TEXT/PLAIN;\n"; #\ 1303 print "$XCS NOTIFY\n" if $ENV{"SECURITY_NOTIFY"} || $ENV{"SECURITY_NOTIFY_VERBOSE"}; #\ 1304 print "$XCS REPORT: Trapped poisoned $typ attachment \"$fn\"\n" if $ENV{"SECURITY_NOTIFY"} || $ENV{"SECURITY_NOTIFY_VERBOSE"}; #\ 1305 print "$XCS QUARANTINE\n" if $ENV{"SECURITY_QUARANTINE"}; #\ 1306 print "Content-Description: SECURITY WARNING\n\n"; #\ 1307 print $ENV{"POISONED_WARNING"}; #\ 1308 print "Scanner score: 0 (poisoned by name, scan skipped)\n\n"; #\ 1309 last; #\ 1310 } #\ 1311 } #\ 1312 close(POISONED); #\ 1313 } else { #\ 1314 warn " ERR: Unable to open poison list \"$specf\".\n"; #\ 1315 } #\ 1316 } #\ 1317 } #\ 1318 if (($bndry) = $hdrtxt =~ /^Content-Type:\s+multipart\/.*\s+boundary\s*=\s*"?([^"]+)"?/i) { #\ 1319 push @mimebdrs, $mimebdry; #\ 1320 push @newbdrs, $newbdry; #\ 1321 push @rawbdrs, $rawbdry; #\ 1322 push @bdrstoolong, $bdrytoolong; #\ 1323 push @gotbdrs, $gotbdry; #\ 1324 push @nullbdrs, $nullbdry; #\ 1325 $mimebdry = $newbdry = $bndry; #\ 1326 $mimebdry = quotemeta($mimebdry); #\ 1327 $rcrsmsg = $bdrytoolong = $gotbdry = 0; #\ 1328 } #\ 1329 if ($hdrtxt =~ /^Content-Type:\s+message\/rfc822/i) { #\ 1330 if (!$inmimehdr) { #\ 1331 push @mimebdrs, $mimebdry; #\ 1332 push @newbdrs, $newbdry; #\ 1333 push @rawbdrs, $rawbdry; #\ 1334 push @bdrstoolong, $bdrytoolong; #\ 1335 push @gotbdrs, $gotbdry; #\ 1336 push @nullbdrs, $nullbdry; #\ 1337 $mimebdry = $newbdry = ""; #\ 1338 $rcrsmsg = $pastmsghdr = $bdrytoolong = $gotbdry = 0; #\ 1339 } else { #\ 1340 $rcrsmsg = 1; #\ 1341 } #\ 1342 } #\ 1343 if ($ENV{"SECURITY_STRIP_MSTNEF"} && $hdrtxt =~ /^Content-Type:\s+application\/MS-TNEF/i) { #\ 1344 print "Content-Type: TEXT/PLAIN;\n"; #\ 1345 print "$XCS REPORT: Stripped MS-TNEF attachment\n"; #\ 1346 print "Content-Description: SECURITY NOTICE\n\n"; #\ 1347 print $ENV{"TNEF_WARNING"}; #\ 1348 $_ = $dfrhdr = $hdrtxt = ""; #\ 1349 $strip_att = 1; #\ 1350 $inmimehdr = 0; #\ 1351 } #\ 1352 while (($filen) = $hdrtxt =~ /^Content-[-\w]+\s*:.*name\s*=\s*"([^"]*\.($ENV{"MANGLE_EXTENSIONS"})(\?=)?)"/io) { #\ 1353 $fn = $filen; $fn =~ s/\s{10,}/ (many spaces) /; #\ 1354 $stripped = 0; #\ 1355 if (!$poisoned && ($specf = $ENV{"STRIPPED_EXECUTABLES"})) { #\ 1356 if (open(STRIPPED,$specf)) { #\ 1357 warn " Checking \"$fn\" for stripping.\n"; #\ 1358 while (chomp($stp_spec = <STRIPPED>)) { #\ 1359 $stp_spec =~ s/^\s+//g; #\ 1360 $stp_spec =~ s/\s.*$//g; #\ 1361 next unless $stp_spec; #\ 1362 $stp_spec =~ s/([^\\])\./$1\\./g; #\ 1363 $stp_spec =~ s/\*/.*/g; #\ 1364 $stp_spec =~ s/\?\?/?./g; #\ 1365 $stp_spec =~ s/([^\(]|^)\?/$1./g; #\ 1366 $stp_spec .= "(\\?=)?\$" unless $stp_spec =~ /\$/; #\ 1367 warn " Checking against \"$stp_spec\"\n" if $ENV{"DEBUG"}; #\ 1368 if ($filen =~ /^${stp_spec}/i) { #\ 1369 warn " Stripped executable \"$fn\".\n"; #\ 1370 print "Content-Type: TEXT/PLAIN;\n"; #\ 1371 print "$XCS REPORT: Attachment \"$fn\" stripped\n"; #\ 1372 print "Content-Description: SECURITY NOTICE\n\n"; #\ 1373 print $ENV{"STRIPPED_WARNING"}; #\ 1374 print "Filename: $fn\n\n"; #\ 1375 print "More headers follow:\n\n" unless $pastmsghdr; #\ 1376 $_ = $dfrhdr = $hdrtxt = ""; #\ 1377 $strip_att = $stripped = 1; #\ 1378 $inmimehdr = $check_att = 0; #\ 1379 last; #\ 1380 } #\ 1381 } #\ 1382 close(STRIPPED); #\ 1383 } else { #\ 1384 warn " ERR: Unable to open stripped-executables file \"$specf\".\n"; #\ 1385 } #\ 1386 } #\ 1387 if (!$poisoned && !$stripped && ($specf = $ENV{"POISONED_EXECUTABLES"})) { #\ 1388 if (open(POISONED,$specf)) { #\ 1389 warn " Checking \"$fn\" for poisoning.\n"; #\ 1390 while (chomp($psn_spec = <POISONED>)) { #\ 1391 $psn_spec =~ s/^\s+//g; #\ 1392 $psn_spec =~ s/\s.*$//g; #\ 1393 next unless $psn_spec; #\ 1394 $psn_spec =~ s/([^\\])\./$1\\./g; #\ 1395 $psn_spec =~ s/\*/.*/g; #\ 1396 $psn_spec =~ s/\?\?/?./g; #\ 1397 $psn_spec =~ s/([^\(]|^)\?/$1./g; #\ 1398 $psn_spec .= "(\\?=)?\$" unless $psn_spec =~ /\$/; #\ 1399 warn " Checking against \"$psn_spec\"\n" if $ENV{"DEBUG"}; #\ 1400 if ($filen =~ /^${psn_spec}/i) { #\ 1401 warn " Trapped poisoned executable \"$fn\".\n"; #\ 1402 $poisoned = 1; $check_att = 0; #\ 1403 print "Content-Type: TEXT/PLAIN;\n"; #\ 1404 print "$XCS NOTIFY\n" if $ENV{"SECURITY_NOTIFY"} || $ENV{"SECURITY_NOTIFY_VERBOSE"}; #\ 1405 print "$XCS REPORT: Trapped poisoned executable \"$fn\"\n" if $ENV{"SECURITY_NOTIFY"} || $ENV{"SECURITY_NOTIFY_VERBOSE"}; #\ 1406 print "$XCS QUARANTINE\n" if $ENV{"SECURITY_QUARANTINE"}; #\ 1407 print "Content-Description: SECURITY WARNING\n\n"; #\ 1408 print $ENV{"POISONED_WARNING"}; #\ 1409 last; #\ 1410 } #\ 1411 } #\ 1412 close(POISONED); #\ 1413 } else { #\ 1414 warn " ERR: Unable to open poisoned-executables file \"$specf\".\n"; #\ 1415 } #\ 1416 } #\ 1417 unless ($stripped) { #\ 1418 warn " Mangling executable filename \"$fn\".\n"; #\ 1419 $newfilen = $filen; $filen = quotemeta($filen); #\ 1420 $newfilen =~ s/\.([-a-z0-9{}]+(\?=)?)$/.${$}DEFANGED-$1/i; #\ 1421 $hdrtxt =~ s/name\s*=\s*"?${filen}"?/name="$newfilen"/ig; #\ 1422 $mangle_mime_type = 1; #\ 1423 } #\ 1424 } #\ 1425 if ($mangle_mime_type && $hdrtxt =~ /^Content-Type:\s/i) { #\ 1426 ($oct) = $hdrtxt =~ /^Content-Type:.*\s(\S+\/\S+;?)/i; #\ 1427 warn " Mangling MIME type \"$oct\".\n"; #\ 1428 unless ($oct =~ /text\/plain;/i) { #\ 1429 print "$XCS original Content-Type was $oct\n"; #\ 1430 $oct = quotemeta($oct); #\ 1431 $hdrtxt =~ s/${oct}/APPLICATION\/DEFANGED;/i; #\ 1432 } #\ 1433 } #\ 1434 if ($mangle_mime_type && $hdrtxt =~ /\sx-mac-\S+/i) { #\ 1435 $eudora = ""; #\ 1436 while (($eh) = $hdrtxt =~ /(\sx-mac-\S+\s*=\s*\S+;?)/i) { #\ 1437 $eudora .= $eh; #\ 1438 $eh = quotemeta($eh); #\ 1439 $hdrtxt =~ s/${eh}//i; #\ 1440 } #\ 1441 print "$XCS removed$eudora\n"; #\ 1442 } #\ 1443 if (($junk) = $hdrtxt =~ /^Content-Type\s*:\s+(.{128}).{100,}$/i) { #\ 1444 warn " Truncating long Content-Type header.\n"; #\ 1445 $junk =~ s/"/\\"/g; #\ 1446 $hdrtxt = "Content-Type: X-BOGUS\/X-BOGUS; originally=\"$junk...\""; #\ 1447 } elsif (($junk) = $hdrtxt =~ /^Content-Description\s*:\s+(.{128}).{100,}$/i) { #\ 1448 warn " Truncating long Content-Description header.\n"; #\ 1449 $hdrtxt = "Content-Description: $junk..."; #\ 1450 } elsif (($junk) = $hdrtxt =~ /^Content-[-\w]+\s*:\s+(.{128}).{100,}$/i) { #\ 1451 warn " Truncating long MIME header.\n"; #\ 1452 $junk =~ s/"/\\"/g; #\ 1453 $hdrtxt =~ s/^Content-([-\w]+)\s*:.*$/X-Overflow: Content-$1; originally="$junk..."/i; #\ 1454 } #\ 1455 $hdrtxt =~ s/\\ÿ/\\"/g; #\ 1456 print "$hdrtxt\n" if $hdrtxt; #\ 1457 $hdrtxt = ""; #\ 1458 if (!$inmimehdr) { #\ 1459 if ($dfrhdr) { #\ 1460 if ($mangle_mime_type && $dfrhdr =~ /^Content-Type:\s/i) { #\ 1461 ($oct) = $dfrhdr =~ /^Content-Type:[^\n]*\s(\S+\/\S+;?)/i; #\ 1462 warn " Mangling MIME type \"$oct\".\n"; #\ 1463 unless ($oct =~ /text\/plain;/i) { #\ 1464 print "$XCS original Content-Type was $oct\n"; #\ 1465 $oct = quotemeta($oct); #\ 1466 $dfrhdr =~ s/${oct}/APPLICATION\/DEFANGED;/i; #\ 1467 } #\ 1468 } #\ 1469 print $dfrhdr; $dfrhdr = ""; #\ 1470 } #\ 1471 $poisoned = $mangle_mime_type = 0; #\ 1472 } #\ 1473 } #\ 1474 if (/^\S/) { #\ 1475 s/\s*\n$//; #\ 1476 $hdrtxt = $_; #\ 1477 $_ = ""; #\ 1478 $hdrcnt++; #\ 1479 } else { #\ 1480 $hdrcnt = 0; #\ 1481 $hdrtxt = ""; #\ 1482 } #\ 1483 } #\ 1484 } #\ 1485 } #\ 1486 ' 2>> $LOGFILE 1487 } 1488 1489 } # ---- END OF SIGNED/ENCRYPTED SKIP 1490 1491 :0 HB 1492 * SECURITY_POISON_WINEXE ?? [^ ] 1493 * !$ ^X-Content-Security: \[${HOST}\] (QUARANTINE|DISCARD) 1494 * ^Content-Transfer-Encoding[ ]*:.*base64 1495 * 9876543210^0 ^Content-Type[ ]*:.*(application|multipart)/[^ ]*[ ]*; 1496 * 9876543210^0 ^Content-Disposition[ ]*:.*attachment 1497 { 1498 # not already quarantined 1499 # check for Windows executable attachments that were not blocked by name 1500 # rules for postfix from <hobbit@avian.org>, adapted to sanitizer 1501 # http://archives.neohapsis.com/archives/postfix/2002-04/1841.html 1502 # NOT 100% reliable, but will catch the simple case of using 1503 # a benign filename and a bogus MIME type, and letting Windows figure out 1504 # to execute the attachment directly (vs. opening in a viewer) by its magic 1505 1506 :0 B D hf 1507 * 9876543210^0 ^TV[nopqr]....[AB]..A.A....*AAAA...*AAAA 1508 * 9876543210^0 ^[^ ]*LnJkYXRhAA 1509 * 9876543210^0 ^[^ ]*cmRhdGEAA 1510 * 9876543210^0 ^[^ ]*5yZGF0YQAA 1511 * 9876543210^0 ^[^ ]*LnJlbG9JAA 1512 * 9876543210^0 ^[^ ]*cmVsb2MAA 1513 * 9876543210^0 ^[^ ]*5yZWxvYwAA 1514 | formail -A "X-Content-Security: [$HOST] NOTIFY" \ 1515 -A "X-Content-Security: [$HOST] QUARANTINE" \ 1516 -A "X-Content-Security: [$HOST] REPORT: Trapped Windows executable attachment" 1517 } 1518 1519 :0 HB 1520 * $ ^X-Content-Security: \[${HOST}\] (NOTIFY|QUARANTINE|DISCARD) 1521 { 1522 :0 1523 * SECURITY_MSGID_LOG ?? [^ ] 1524 { JUNK=`echo "$MSGID" >> $SECURITY_MSGID_LOG` } 1525 1526 :0 HB 1527 * $ ^X-Content-Security: \[${HOST}\] DISCARD 1528 { 1529 SECURITY_QUARANTINE=/dev/null 1530 } 1531 1532 :0 HB 1533 * $ ^X-Content-Security: \[${HOST}\] SPOOFED_SENDER 1534 { 1535 # don't bother the sender, it's probably forged 1536 SECURITY_NOTIFY_SENDER= 1537 } 1538 1539 :0 1540 * 9876543210^0 SECURITY_NOTIFY ?? [^ ] 1541 * 9876543210^0 SECURITY_NOTIFY_VERBOSE ?? [^ ] 1542 { 1543 # Notify administration and sender of the attack 1544 1545 STATUS="STATUS: Message delivered to $TO" 1546 STATUS_PUBLIC="STATUS: Message delivered." 1547 REPORT="REPORT: No details available." 1548 SCORE="REPORT: Not a document, or already poisoned by filename. Not scanned for macros." 1549 1550 :0 1551 * SECURITY_QUARANTINE ?? [^ ] 1552 { 1553 STATUS="STATUS: Message quarantined in $SECURITY_QUARANTINE, not delivered to recipient." 1554 STATUS_PUBLIC="STATUS: Message quarantined, not delivered to recipient." 1555 } 1556 1557 :0 HB 1558 * $ ^X-Content-Security: \[${HOST}\] DISCARD 1559 { 1560 STATUS="STATUS: Message discarded, not delivered to recipient." 1561 STATUS_PUBLIC="$STATUS" 1562 } 1563 1564 :0 HB 1565 * ^\/Macro Scanner score: [1-9][0-9]+ 1566 { 1567 SCORE="REPORT: $MATCH" 1568 } 1569 1570 :0 HB 1571 * $ ^X-Content-Security: \[${HOST}\] REPORT: 1572 { REPORT=`grep "^X-Content-Security: \[${HOST}\] REPORT: " | sed -e 's/^.* REPORT:/REPORT:/g'` } 1573 1574 1575 #--------------------------------------------------------------------------- 1576 # Smart Sender Notify Suppression 1577 # If the Return-Path: domain is not supported by any of the Received: domains 1578 # then the envelope sender address is probably forged. Don't waste time notifying. 1579 # 1580 1581 :0 1582 * SECURITY_NOTIFY_SENDER ?? [^ ] 1583 * FROMDOM ?? [^ ] 1584 * ! SECURITY_DISABLE_SMART_REPLY ?? [^ ] 1585 { 1586 FROMDOM2="_" 1587 FROMDOM3="_" 1588 1589 :0 1590 * FROMDOM ?? ^.+\.\/[^.]+\.[^.]+$ 1591 { 1592 FROMDOM2="$MATCH" 1593 # ignore some domains (e.g. ac.uk, com.ar, etc.) 1594 :0 1595 * FROMDOM2 ?? ^(co|ac|com|net|org)\.[a-z][a-z]$ 1596 { 1597 FROMDOM2="_" 1598 } 1599 } 1600 1601 :0 1602 * FROMDOM ?? ^.+\.\/[^.]+\.[^.]+\.[^.]+$ 1603 { 1604 FROMDOM3="$MATCH" 1605 } 1606 1607 # Look for Received: headers that support the sender's claimed domain 1608 # Line 1: sendmail, postfix 1609 # Line 2: qmail 1610 :0 H 1611 * $! ^Received: from [^ ]+ \(([^ .]+\.)*($FROMDOM|$FROMDOM3|$FROMDOM2) [^)]+\)+[ ]+by (${SECURITY_TRUSTED_MTAS:-${HOST}}) 1612 * $! ^Received: from ([^ .]+\.)*($FROMDOM|$FROMDOM3|$FROMDOM2)[ ]+\(HELO .*[ ]+by (${SECURITY_TRUSTED_MTAS:-${HOST}}) 1613 { 1614 REPLY_SUPPRESSED="NOTICE: Envelope sender domain $FROMDOM not supported by trusted Received: path. Suppressing sender notification.${NL}" 1615 LOG=" $REPLY_SUPPRESSED" 1616 SECURITY_NOTIFY_SENDER= 1617 } 1618 1619 # Did a mailing list rewrite the Return-Path header? 1620 :0E 1621 * -1^0 1622 * 1^0 ^Precedence: (bulk|junk|list) 1623 * 1^0 ^(List-Id|X-Mailing-List): 1624 * 9876543210^0 FROM ?? \<owner- 1625 * 9876543210^0 FROM ?? \<[^@ >]+-l-admin@ 1626 { 1627 REPLY_SUPPRESSED="NOTICE: Message from mailing list. Suppressing sender notification.${NL}" 1628 LOG=" $REPLY_SUPPRESSED" 1629 SECURITY_NOTIFY_SENDER= 1630 } 1631 } 1632 #--------------------------------------------------------------------------- 1633 1634 :0 HB 1635 * !$ ^X-Content-Security: \[${HOST}\] NONOTIFY 1636 { 1637 :0 1638 * SECURITY_NOTIFY ?? [^ ] 1639 * !$ ^X-Loop: EMAIL SECURITY WARNING $HOST $SECRET 1640 { 1641 LOG="${NL} NOTIFY ADMIN ($SECURITY_NOTIFY)${NL}" 1642 1643 :0 h ci 1644 | ( \ 1645 echo "To: $SECURITY_NOTIFY";\ 1646 echo 'From: "Procmail Security daemon"' "<${SECURITY_LOCAL_POSTMASTER}>";\ 1647 echo 'Subject: SECURITY WARNING - possible email attack';\ 1648 echo "X-Loop: EMAIL SECURITY WARNING $HOST $SECRET"; \ 1649 echo ;\ 1650 echo "\$Revision: 1.151 $";\ 1651 echo ;\ 1652 echo "$REPORT";\ 1653 echo "$SCORE";\ 1654 echo "$STATUS";\ 1655 echo "$REPLY_SUPPRESSED";\ 1656 echo ;\ 1657 echo 'Headers from message:';\ 1658 echo ;\ 1659 sed -e 's/^/> /' ;\ 1660 ) | $SENDMAIL $MTA_FLAGS_CMDLN $SECURITY_NOTIFY 1661 } 1662 1663 :0 1664 * SECURITY_NOTIFY_VERBOSE ?? [^ ] 1665 * !$ ^X-Loop: EMAIL SECURITY WARNING $HOST $SECRET 1666 { 1667 LOG="${NL} NOTIFY ADMIN VERBOSE ($SECURITY_NOTIFY_VERBOSE)${NL}" 1668 1669 :0 hb ci 1670 | ( \ 1671 echo "To: $SECURITY_NOTIFY_VERBOSE";\ 1672 echo 'From: "Procmail Security daemon"' "<${SECURITY_LOCAL_POSTMASTER}>";\ 1673 echo 'Subject: SECURITY WARNING - possible email attack';\ 1674 echo "X-Loop: EMAIL SECURITY WARNING $HOST $SECRET"; \ 1675 echo ;\ 1676 echo "$REPORT";\ 1677 echo "$SCORE";\ 1678 echo "$STATUS";\ 1679 echo "$REPLY_SUPPRESSED";\ 1680 echo ;\ 1681 echo 'Message:';\ 1682 echo ;\ 1683 sed -e 's/^/> /' ;\ 1684 ) | $SENDMAIL $MTA_FLAGS_CMDLN $SECURITY_NOTIFY_VERBOSE 1685 } 1686 } 1687 1688 :0 H 1689 * SECURITY_NOTIFY_SENDER ?? [^ ] 1690 * ! ^FROM_DAEMON 1691 * !$ ^X-Loop: EMAIL SECURITY WARNING $HOST $SECRET 1692 { 1693 LOG="${NL} NOTIFY SENDER${NL}" 1694 PM_BCC="X-Placeholder:" 1695 PM_CC="X-Placeholder:" 1696 1697 :0 HB 1698 * !$ ^X-Content-Security: \[${HOST}\] NONOTIFY 1699 { 1700 PM_BCC="Bcc: $SECURITY_NOTIFY" 1701 } 1702 1703 :0 1704 * SECURITY_NOTIFY_SENDER_POSTMASTER ?? [^ ] 1705 * FROMDOM ?? [^ ] 1706 { 1707 PM_CC="postmaster@$FROMDOM" 1708 1709 :0 1710 * SECURITY_NOTIFY_SENDER_ABUSE ?? [^ ] 1711 { 1712 PM_CC="$PM_CC>, <abuse@$FROMDOM" 1713 } 1714 1715 PM_CC="Cc: <$PM_CC>" 1716 } 1717 1718 MSG_HEADERS=`sed -e '/^$/q; s/^/> /'` 1719 1720 :0 h ci 1721 | ( \ 1722 formail -r \ 1723 -I "From: \"Procmail Security daemon\" <${SECURITY_LOCAL_POSTMASTER}>"\ 1724 -I "$PM_BCC" -I "$PM_CC" -I "References: $MSGID" \ 1725 -I "Sender: <${SECURITY_LOCAL_POSTMASTER}>" \ 1726 -I "Errors-To: <${SECURITY_LOCAL_POSTMASTER}>" \ 1727 -I "X-Loop: EMAIL SECURITY WARNING $HOST $SECRET" \ 1728 -I "$OVERRIDEFORMAIL" \ 1729 ;\ 1730 echo ;\ 1731 if [ -f "$SECURITY_NOTIFY_SENDER" -a -s "$SECURITY_NOTIFY_SENDER" -a -r "$SECURITY_NOTIFY_SENDER" ] ;\ 1732 then \ 1733 echo 'Regarding your message to';\ 1734 echo "$TO";\ 1735 echo ;\ 1736 cat $SECURITY_NOTIFY_SENDER; \ 1737 else \ 1738 echo '*** SECURITY WARNING ***';\ 1739 echo 'Our email gateway has detected that your message to';\ 1740 echo "$TO";\ 1741 echo 'MAY contain hazardous embedded scripting or attachments,';\ 1742 echo 'or has been rejected by our site security policy for some other reason.';\ 1743 echo 'If you have a question, please reply to this notification message.';\ 1744 echo ;\ 1745 echo 'It is POSSIBLE that your message was infected by a virus.';\ 1746 echo 'You should make sure your virus signature file';\ 1747 echo 'is up-to-date and then rescan your computer,';\ 1748 echo 'especially if you do not remember sending this message.';\ 1749 echo ;\ 1750 echo 'If the macro scanner score is large yet your virus scanner reports';\ 1751 echo 'that the document is not infected, try saving it using a different';\ 1752 echo 'format (such as Rich Text - "RTF") that will remove all macros.';\ 1753 fi ;\ 1754 echo ;\ 1755 echo "$REPORT";\ 1756 echo "$SCORE";\ 1757 echo "$STATUS_PUBLIC";\ 1758 echo ;\ 1759 echo 'Headers from message:';\ 1760 echo ;\ 1761 echo "$MSG_HEADERS";\ 1762 echo ;\ 1763 echo ;\ 1764 echo '--';\ 1765 echo 'Message sanitized on' $HOST;\ 1766 echo 'See http://www.impsec.org/email-tools/sanitizer-intro.html for details.';\ 1767 echo ;\ 1768 ) | $SENDMAIL $MTA_FLAGS_HDRS 1769 1770 } 1771 } 1772 1773 :0 1774 * SECURITY_QUARANTINE ?? [^ ] 1775 { 1776 :0 1777 * SECURITY_NOTIFY_RECIPIENT ?? [^ ] 1778 { 1779 LOG="${NL} NOTIFY RECIPIENT${NL}" 1780 1781 # We could stuff this directly into $DEFAULT but then 1782 # we'd have to worry about generating Message-ID and Date headers 1783 # and it wouldn't work on a relay... 1784 :0 h ci 1785 | ( \ 1786 echo "To: $TO";\ 1787 echo 'From: "Procmail Security daemon"' "<${SECURITY_LOCAL_POSTMASTER}>";\ 1788 if [ "$SECURITY_QUARANTINE" = "/dev/null" ] ;\ 1789 then \ 1790 echo 'Subject: SECURITY WARNING - email discarded';\ 1791 else \ 1792 echo 'Subject: SECURITY WARNING - email quarantined';\ 1793 fi ;\ 1794 echo ;\ 1795 if [ -f "$SECURITY_NOTIFY_RECIPIENT" -a -s "$SECURITY_NOTIFY_RECIPIENT" -a -r "$SECURITY_NOTIFY_RECIPIENT" ] ;\ 1796 then \ 1797 cat $SECURITY_NOTIFY_RECIPIENT; \ 1798 else \ 1799 echo '*** SECURITY WARNING ***';\ 1800 echo 'Our email gateway has detected that a message sent to you';\ 1801 echo 'MAY contain hazardous embedded scripting or attachments.';\ 1802 echo 'The message has been quarantined or discarded per our site security policy.';\ 1803 echo 'Please contact your system administrator for further details.';\ 1804 echo ;\ 1805 fi ;\ 1806 echo ;\ 1807 echo "$REPORT";\ 1808 echo "$SCORE";\ 1809 echo "$STATUS_PUBLIC";\ 1810 echo ;\ 1811 echo 'Headers from message:';\ 1812 echo ;\ 1813 sed -e 's/^/> /' ;\ 1814 echo ;\ 1815 echo '--';\ 1816 echo 'Message sanitized on' $HOST;\ 1817 echo 'See http://www.impsec.org/email-tools/sanitizer-intro.html for details.';\ 1818 echo ;\ 1819 ) | $SENDMAIL $MTA_FLAGS_HDRS 1820 1821 } 1822 1823 :0 :${SECURITY_QUARANTINE_LOCKFILE} 1824 $SECURITY_QUARANTINE 1825 1826 :0 e 1827 * ! SECURITY_QUARANTINE_OPTIONAL ?? [^ ] 1828 { 1829 # Argh! Quarantine failed, and not explicitly marked as optional! 1830 # Bounce message, and notify administrator 1831 LOG="${NL} ERR: QUARANTINE FAILED!${NL}" 1832 EXITCODE=65 1833 1834 :0 h 1835 * SECURITY_NOTIFY ?? [^ ] 1836 * !$ ^X-Loop: EMAIL SECURITY WARNING $HOST $SECRET 1837 | ( \ 1838 echo "To: $SECURITY_NOTIFY";\ 1839 echo 'From: "Procmail Security daemon"' "<${SECURITY_LOCAL_POSTMASTER}>";\ 1840 echo 'Subject: SECURITY WARNING - quarantine failed!';\ 1841 echo "X-Loop: EMAIL SECURITY WARNING $HOST $SECRET"; \ 1842 echo ;\ 1843 echo 'Attempt to quarantine the following message in $SECURITY_QUARANTINE failed.';\ 1844 echo 'Message has been bounced.';\ 1845 echo 'Verify file access permissions (file must be writable):';\ 1846 ls -l $SECURITY_QUARANTINE ;\ 1847 echo ;\ 1848 echo "$REPORT";\ 1849 echo "$SCORE";\ 1850 echo ;\ 1851 echo 'Headers from message:';\ 1852 echo ;\ 1853 sed -e 's/^/> /' ;\ 1854 ) | $SENDMAIL $MTA_FLAGS_CMDLN $SECURITY_NOTIFY 1855 1856 # zap it, just in case 1857 :0 1858 /dev/null 1859 } 1860 } 1861 } 1862 1863 #eof