├── .gitattributes ├── .gitignore ├── .travis.yml ├── Bachelorarbeit - Traub, Stephan.pdf ├── LICENSE.md ├── MANIFEST ├── MacroMilter.sln ├── README.md ├── README.opensuse ├── macromilter ├── MANIFEST ├── MacroMilter.conf ├── MacroMilter.pyproj ├── __init__.py ├── config.ini ├── install_ubuntu.sh ├── macromilter.logrotate ├── macromilter.py ├── macromilter.service └── update.sh ├── test_mails ├── 01_mail_without_attachment.eml ├── 02_mail_with_infected_word_document.eml ├── 04_mail_with_infected_word_and_clean_zip.eml ├── 05_mail_with_both_infected_and_not_word_in_zip.eml ├── Dok1.doc ├── TestDoc.zip ├── Test_Dock_Block.doc ├── Test_Dock_Block.zip ├── Test_Dock_Block_2x.zip ├── clean_wordfile.docx ├── clean_wordfile.zip ├── encrypted.zip ├── rechnung-postmaster789.zip └── zipwithinfectedandnotinfectedword.zip └── travis ├── MacroMilter.conf ├── config_1.ini ├── config_2.ini ├── config_3.ini ├── mailalias.sh ├── readmail.py ├── setup.sh ├── tests.sh └── tests_zip.sh /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | *Doc1.doc 10 | 11 | # User-specific files (MonoDevelop/Xamarin Studio) 12 | *.userprefs 13 | 14 | # Build results 15 | [Dd]ebug/ 16 | [Dd]ebugPublic/ 17 | [Rr]elease/ 18 | [Rr]eleases/ 19 | x64/ 20 | x86/ 21 | build/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | 26 | # Visual Studio 2015 cache/options directory 27 | .vs/ 28 | 29 | # MSTest test Results 30 | [Tt]est[Rr]esult*/ 31 | [Bb]uild[Ll]og.* 32 | 33 | # NUNIT 34 | *.VisualState.xml 35 | TestResult.xml 36 | 37 | # Build Results of an ATL Project 38 | [Dd]ebugPS/ 39 | [Rr]eleasePS/ 40 | dlldata.c 41 | 42 | # DNX 43 | project.lock.json 44 | artifacts/ 45 | 46 | *_i.c 47 | *_p.c 48 | *_i.h 49 | *.ilk 50 | *.meta 51 | *.obj 52 | *.pch 53 | *.pdb 54 | *.pgc 55 | *.pgd 56 | *.rsp 57 | *.sbr 58 | *.tlb 59 | *.tli 60 | *.tlh 61 | *.tmp 62 | *.tmp_proj 63 | *.log 64 | *.vspscc 65 | *.vssscc 66 | .builds 67 | *.pidb 68 | *.svclog 69 | *.scc 70 | 71 | # Chutzpah Test files 72 | _Chutzpah* 73 | 74 | # Visual C++ cache files 75 | ipch/ 76 | *.aps 77 | *.ncb 78 | *.opensdf 79 | *.sdf 80 | *.cachefile 81 | 82 | # Visual Studio profiler 83 | *.psess 84 | *.vsp 85 | *.vspx 86 | 87 | # TFS 2012 Local Workspace 88 | $tf/ 89 | 90 | # Guidance Automation Toolkit 91 | *.gpState 92 | 93 | # ReSharper is a .NET coding add-in 94 | _ReSharper*/ 95 | *.[Rr]e[Ss]harper 96 | *.DotSettings.user 97 | 98 | # JustCode is a .NET coding add-in 99 | .JustCode 100 | 101 | # TeamCity is a build add-in 102 | _TeamCity* 103 | 104 | # DotCover is a Code Coverage Tool 105 | *.dotCover 106 | 107 | # NCrunch 108 | _NCrunch_* 109 | .*crunch*.local.xml 110 | 111 | # MightyMoose 112 | *.mm.* 113 | AutoTest.Net/ 114 | 115 | # Web workbench (sass) 116 | .sass-cache/ 117 | 118 | # Installshield output folder 119 | [Ee]xpress/ 120 | 121 | # DocProject is a documentation generator add-in 122 | DocProject/buildhelp/ 123 | DocProject/Help/*.HxT 124 | DocProject/Help/*.HxC 125 | DocProject/Help/*.hhc 126 | DocProject/Help/*.hhk 127 | DocProject/Help/*.hhp 128 | DocProject/Help/Html2 129 | DocProject/Help/html 130 | 131 | # Click-Once directory 132 | publish/ 133 | 134 | # Publish Web Output 135 | *.[Pp]ublish.xml 136 | *.azurePubxml 137 | ## TODO: Comment the next line if you want to checkin your 138 | ## web deploy settings but do note that will include unencrypted 139 | ## passwords 140 | #*.pubxml 141 | 142 | *.publishproj 143 | 144 | # NuGet Packages 145 | *.nupkg 146 | # The packages folder can be ignored because of Package Restore 147 | **/packages/* 148 | # except build/, which is used as an MSBuild target. 149 | !**/packages/build/ 150 | # Uncomment if necessary however generally it will be regenerated when needed 151 | #!**/packages/repositories.config 152 | 153 | # Windows Azure Build Output 154 | csx/ 155 | *.build.csdef 156 | 157 | # Windows Store app package directory 158 | AppPackages/ 159 | 160 | # Visual Studio cache files 161 | # files ending in .cache can be ignored 162 | *.[Cc]ache 163 | # but keep track of directories ending in .cache 164 | !*.[Cc]ache/ 165 | 166 | # Others 167 | ClientBin/ 168 | [Ss]tyle[Cc]op.* 169 | ~$* 170 | *~ 171 | *.dbmdl 172 | *.dbproj.schemaview 173 | *.pfx 174 | *.publishsettings 175 | node_modules/ 176 | orleans.codegen.cs 177 | 178 | # RIA/Silverlight projects 179 | Generated_Code/ 180 | 181 | # Backup & report files from converting an old project file 182 | # to a newer Visual Studio version. Backup files are not needed, 183 | # because we have git ;-) 184 | _UpgradeReport_Files/ 185 | Backup*/ 186 | UpgradeLog*.XML 187 | UpgradeLog*.htm 188 | 189 | # SQL Server files 190 | *.mdf 191 | *.ldf 192 | 193 | # Business Intelligence projects 194 | *.rdl.data 195 | *.bim.layout 196 | *.bim_*.settings 197 | 198 | # Microsoft Fakes 199 | FakesAssemblies/ 200 | 201 | # Node.js Tools for Visual Studio 202 | .ntvs_analysis.dat 203 | 204 | # Visual Studio 6 build log 205 | *.plg 206 | 207 | # Visual Studio 6 workspace options file 208 | *.opt 209 | 210 | # LightSwitch generated files 211 | GeneratedArtifacts/ 212 | _Pvt_Extensions/ 213 | ModelManifest.xml 214 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: phyton 2 | 3 | python: 4 | - "2.7" 5 | 6 | before_install: 7 | - sudo apt-get update -qq 8 | - sudo apt-get install -y -qq postfix libmilter-dev libmilter1.0.1 sendemail mutt 9 | # install dependencies 10 | - sudo pip install oletools 11 | - sudo pip install pymilter 12 | - sudo pip install configparser 13 | - sudo pip install olefile 14 | - sudo pip install email 15 | 16 | before_script: 17 | - sudo service postfix stop 18 | - sudo postconf -e smtpd_milters=inet:127.0.0.1:10025 19 | - sudo postconf -e milter_default_action=accept 20 | - sudo postconf -e default_privs=travis 21 | - sudo postconf 22 | - sudo service postfix start 23 | - sudo iptables -I INPUT -s 127.0.0.1 -j ACCEPT 24 | - sudo iptables -I OUTPUT -s 127.0.0.1 -j ACCEPT 25 | # install macromilter 26 | - sudo mkdir /etc/macromilter/ 27 | - sudo mkdir -p /var/log/macromilter/ 28 | - sudo cp ./macromilter/macromilter.py /usr/bin/ 29 | - sudo cp ./travis/MacroMilter.conf /etc/init/ 30 | - sudo init-checkconf /etc/init/MacroMilter.conf 31 | - sudo initctl log-priority debug 32 | - sudo initctl reload-configuration 33 | - sudo chown postfix:postfix -R /etc/macromilter 34 | - sudo chown postfix:postfix -R /var/log/macromilter 35 | - sudo chown postfix:postfix /usr/bin/macromilter.py 36 | - sudo chmod +x /usr/bin/macromilter.py 37 | - sudo cat /etc/init/MacroMilter.conf 38 | - sudo /bin/bash ./travis/mailalias.sh 39 | - sudo ls -la ./travis 40 | # start tests 41 | 42 | jobs: 43 | include: 44 | # Default config 45 | - stage: Testing 46 | script: 47 | - sudo cp ./travis/config_1.ini /etc/macromilter/config.ini 48 | - sudo cat /etc/macromilter/config.ini 49 | - sudo service MacroMilter start 50 | - sleep 10 51 | - sudo service MacroMilter status 52 | - /bin/bash ./travis/tests.sh 53 | - sudo tail -n 100 /var/log/mail.log 54 | - sudo tail -n 100 /home/travis/macromilter.log 55 | # Zip test 56 | - stage: Testing 57 | script: 58 | - sudo cp ./travis/config_1.ini /etc/macromilter/config.ini 59 | - sudo cat /etc/macromilter/config.ini 60 | - sudo service MacroMilter restart 61 | - sleep 10 62 | - sudo service MacroMilter status 63 | - /bin/bash ./travis/tests_zip.sh 64 | - sudo tail -n 100 /var/log/mail.log 65 | - sudo tail -n 100 /home/travis/macromilter.log 66 | # encrypted zip 67 | - stage: Testing 68 | script: 69 | - sudo cp ./travis/config_1.ini /etc/macromilter/config.ini 70 | - sudo cat /etc/macromilter/config.ini 71 | - sudo service MacroMilter restart 72 | - sleep 10 73 | - sudo service MacroMilter status 74 | - sendemail -f travis@localhost -t test@localhost -m "test" -s localhost -u "test" -a ./test_mails/encrypted.zip -v 75 | - sudo tail -n 100 /var/log/mail.log 76 | - sudo tail -n 100 /home/travis/macromilter.log 77 | # Macro wihtelist 78 | - stage: Testing 79 | script: 80 | - sudo cp ./travis/config_2.ini /etc/macromilter/config.ini 81 | - sudo cat /etc/macromilter/config.ini 82 | - sudo service MacroMilter restart 83 | - sleep 10 84 | - sudo service MacroMilter status 85 | - sendemail -f travis@localhost -t test@localhost -m "test" -s localhost -u "test" -a ./test_mails/Test_Dock_Block.doc -v 86 | - sudo tail -n 100 /var/log/mail.log 87 | - sudo tail -n 100 /home/travis/macromilter.log 88 | - sudo cat ./travis/mail.txt 89 | # Reject no 90 | - stage: Testing 91 | script: 92 | - sudo cp ./travis/config_3.ini /etc/macromilter/config.ini 93 | - sudo cat /etc/macromilter/config.ini 94 | - sudo service MacroMilter restart 95 | - sleep 10 96 | - sudo service MacroMilter status 97 | - sendemail -f travis@localhost -t test@localhost -m "test" -s localhost -u "test" -a ./test_mails/Test_Dock_Block.doc -v 98 | - sudo tail -n 100 /var/log/mail.log 99 | - sudo tail -n 100 /home/travis/macromilter.log 100 | - sudo cat ./travis/mail.txt 101 | # Reject no and blacklist 102 | - stage: Testing 103 | script: 104 | - sudo cp ./travis/config_3.ini /etc/macromilter/config.ini 105 | - sudo cat /etc/macromilter/config.ini 106 | - sudo service MacroMilter restart 107 | - sleep 10 108 | - sudo service MacroMilter status 109 | - sendemail -f travis@localhost -t test@localhost -m "test" -s localhost -u "test" -a ./test_mails/Test_Dock_Block.doc -v 110 | - sendemail -f travis@localhost -t test@localhost -m "test" -s localhost -u "test" -a ./test_mails/Test_Dock_Block.doc -v 111 | - sudo tail -n 100 /var/log/mail.log 112 | - sudo tail -n 100 /home/travis/macromilter.log 113 | - sudo cat ./travis/mail.txt 114 | # Normal mail without attachment 115 | - stage: Testing 116 | script: 117 | - sudo cp ./travis/config_3.ini /etc/macromilter/config.ini 118 | - sudo cat /etc/macromilter/config.ini 119 | - sudo service MacroMilter restart 120 | - sleep 10 121 | - sudo service MacroMilter status 122 | - sendemail -f travis@localhost -t test@localhost -m "test" -s localhost -u "test" -v 123 | - sudo tail -n 100 /var/log/mail.log 124 | - sudo tail -n 100 /home/travis/macromilter.log 125 | - sudo cat ./travis/mail.txt 126 | 127 | -------------------------------------------------------------------------------- /Bachelorarbeit - Traub, Stephan.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbidy/MacroMilter/015fff038686a159e7e6696c6fb3236f0847f449/Bachelorarbeit - Traub, Stephan.pdf -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Stephan Traub 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | # file GENERATED by distutils, do NOT edit 2 | setup.py 3 | -------------------------------------------------------------------------------- /MacroMilter.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.25928.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "macromilter", "macromilter\MacroMilter.pyproj", "{15628F71-C607-4AE4-8167-68EB50A18409}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {15628F71-C607-4AE4-8167-68EB50A18409}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {15628F71-C607-4AE4-8167-68EB50A18409}.Release|Any CPU.ActiveCfg = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Version 3.6.1 2 | [![Build Status](https://travis-ci.com/sbidy/MacroMilter.svg?branch=master)](https://travis-ci.com/sbidy/MacroMilter) 3 | 4 | Changelog 3.6.1: 5 | - Fix for tmp filehandle error 6 | - Add new config option for dumping the mail body for forensics 7 | 8 | Changelog 3.6: 9 | - fixing multiple issues and bugs (#41 , #38, #37, #35, #31, #30) 10 | - Add MIME header for the different stages. 11 | - The hash db is updated to SHA256 instead of MD5. Old MD5 hashes still supported. 12 | 13 | ## Contributing 14 | I need some code review and help to make this milter better! If you find some bugs or the code is "creepy" -> feel free to contribute :) 15 | To contribute, please fork this repository and make pull requests to the master or testing branch. 16 | 17 | ## Branches 18 | #### master = production grade and tested implementation 19 | #### testing = only for testing and non-prod. environments 20 | 21 | ## Abstract 22 | This python based milter for the Sendmail and Postfix e-mail servers (mail-filter) checks an incoming mail for MS 20xx Office attachments. If a MS Office file is attached to the mail it will be scanned for suspicious VBA macro code. Documents with malicious macros are removed and replaced by harmless text files or will be rejected to the sender (see config.ini). 23 | 24 | ### Supported Office formats: 25 | - Word 97-2003 (.doc, .dot), Word 2007+ (.docm, .dotm) 26 | - Excel 97-2003 (.xls), Excel 2007+ (.xlsm, .xlsb) 27 | - PowerPoint 97-2003 (.ppt), PowerPoint 2007+ (.pptm, .ppsm) 28 | - Word 2003 XML (.xml) 29 | - Word/Excel Single File Web Page / MHTML (.mht) 30 | - Publisher (.pub) 31 | 32 | Paper (german only) -> [download link](https://github.com/sbidy/MacroMilter/blob/master/Bachelorarbeit%20-%20Traub%2C%20Stephan.pdf) 33 | 34 | Video / Talk (german only) -> [HdM-events](http://events.mi.hdm-stuttgart.de/2016-06-29-mi-pr%C3%A4sentationstag-ss16/MacroMilter%3A%20Malware-Filter%20f%C3%BCr%20E-Mails) 35 | 36 | Chemnitzer Linux Tage 2018 - Talk -> [CLT2018](https://chemnitzer.linux-tage.de/2018/de/programm/beitrag/304) 37 | 38 | *The repo is optimized for Visual Studio* 39 | ## Features 40 | * Parsing VBA macros for suspicious code and function calls 41 | * Uses the milter interface at postfix and sendmail 42 | * Easy to implement 43 | * Not based on virus heuristics (high detection rate) 44 | * Whitelisting 45 | * Creates a hashtable for already scanned files (prevents rescans) 46 | * Runs at the pre-queue at postfix 47 | 48 | ## Dependencies 49 | This milter use the functionality from the oletools (https://bitbucket.org/decalage/oletools) and pymilter (https://pythonhosted.org/milter/) projects. 50 | 51 | ## Installation 52 | 53 | ### Debian and Ubuntu 54 | Download the "install_ubuntu.sh" script from the repo - [install_ubuntu.sh](https://raw.githubusercontent.com/sbidy/MacroMilter/master/macromilter/install_ubuntu.sh). It creates and downloads all required files and packages. 55 | *Please use for Ubunut 14.10 and higher the "old" systemd script part! For 14.0x and older please use the upstart part!* 56 | 57 | ### Fedora 58 | ```bash 59 | dnf install macromilter 60 | systemctl enable --now macromilter.service 61 | 62 | postconf -e smtpd_milters=inet:127.0.0.1:3690 milter_default_action=accept 63 | systemctl reload postfix.service 64 | ``` 65 | 66 | ### Red Hat Enterprise Linux and CentOS 67 | ```bash 68 | yum install epel-release # Only if EPEL is not already enabled 69 | 70 | yum install macromilter 71 | systemctl enable --now macromilter.service 72 | 73 | postconf -e smtpd_milters=inet:127.0.0.1:3690 milter_default_action=accept 74 | systemctl reload postfix.service 75 | ``` 76 | 77 | ## User whitelist 78 | To allow a user or whole domain to send false-positive VAB-Macro-Mails, enter only the user mail address (xyz@domain.com) or the domain (@domain.com). See config.ini for more details. 79 | 80 | ## Macro whitelist 81 | To allow only a special and wellknown macro code, add the SHA256 hash to the Macrohash part in the configuration file. 82 | You will find the raw macro hash in the macromilter log file `INFO [ID] The macro hash is: `. Please use only this one! Keep in mind, that the file hash has also to be deleted form the "hashdatabse". 83 | 84 | ## TBD 85 | * Config-File error handling 86 | * HTML-Dashboard 87 | * Setup-package for pip 88 | 89 | ## Authors 90 | Stephan Traub - Sbidy -> https://github.com/sbidy 91 | 92 | Robert Scheck - robert-scheck -> https://www.robert-scheck.de/ 93 | 94 | ## Credits 95 | Philippe Lagadec https://github.com/decalage2 - oletools 96 | 97 | Stuart D. Gathman https://github.com/sdgathman - pymilter 98 | 99 | ## License 100 | The MIT License (MIT) 101 | 102 | -------------------------------------------------------------------------------- /README.opensuse: -------------------------------------------------------------------------------- 1 | zypper in python-devel sendmail-devel gcc python-pip git 2 | pip install pymilter 3 | pip install oletools 4 | git clone https://github.com/sbidy/MacroMilter 5 | #git clone https://github.com/Gulaschcowboy/MacroMilter 6 | mkdir -p /etc/macromilter/ 7 | mkdir -p /var/log/macromilter/ 8 | cp MacroMilter/MacroMilter/macromilter.py /etc/macromilter/ 9 | cp MacroMilter/MacroMilter/macromilter.service /etc/systemd/system/ 10 | cp MacroMilter/MacroMilter/macromilter.logrotate /etc/logrotate.d/ 11 | 12 | touch /etc/macromilter/whitelist.list 13 | chown postfix:postfix /var/log/macromilter/ 14 | 15 | systemctl daemon-reload 16 | systemctl start macromilter.service 17 | systemctl status macromilter.service 18 | systemctl enable macromilter.service 19 | 20 | postconf -e smtpd_milters=inet:127.0.0.1:3690 21 | postconf -e milter_default_action=accept 22 | 23 | rcpostfix reload 24 | 25 | -------------------------------------------------------------------------------- /macromilter/MANIFEST: -------------------------------------------------------------------------------- 1 | # file GENERATED by distutils, do NOT edit 2 | macromilter.py 3 | setup.py 4 | test/test_macromilter.py 5 | -------------------------------------------------------------------------------- /macromilter/MacroMilter.conf: -------------------------------------------------------------------------------- 1 | description "MacroMilter Service" 2 | 3 | start on runlevel [234] 4 | stop on runlevel [0156] 5 | 6 | setuid postfix 7 | setgid postfix 8 | 9 | chdir /usr/bin/ 10 | exec /usr/bin/python macromilter.py 11 | respawn 12 | -------------------------------------------------------------------------------- /macromilter/MacroMilter.pyproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | 2.0 6 | 15628f71-c607-4ae4-8167-68eb50a18409 7 | 8 | 9 | macromilter.py 10 | 11 | 12 | . 13 | . 14 | macromilter 15 | MacroMilter 16 | 17 | 18 | true 19 | false 20 | 21 | 22 | true 23 | false 24 | 25 | 26 | 27 | 28 | Code 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 10.0 39 | 40 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /macromilter/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbidy/MacroMilter/015fff038686a159e7e6696c6fb3236f0847f449/macromilter/__init__.py -------------------------------------------------------------------------------- /macromilter/config.ini: -------------------------------------------------------------------------------- 1 | [Milter] 2 | # at postfix smtpd_milters = inet:127.0.0.1:3690 3 | # bind to unix or tcp socket "inet:port@ip" or "///.sock" 4 | SOCKET = inet:3690@127.0.0.1 5 | # Set umask for unix socket, e.g. 0077 for group writable 6 | UMASK = 0077 7 | # Milter timout in seconds 8 | TIMEOUT = 30 9 | # Define the max size for each message in bytes (~50MB) 10 | MAX_FILESIZE = 50000000 11 | # Reject error message 12 | MESSAGE = ERROR - Attachment contains unallowed office macros! 13 | # Reject the mail if a malware macro is detected (yes/no) 14 | REJECT_MESSAGE = yes 15 | # Max nested archive depth - recommendation = 5 16 | MAX_ZIP = 5 17 | # If the message will be rejected - dump the mail body to file? 18 | DUMP_BODY = yes 19 | 20 | [Logging] 21 | LOGFILE_DIR = /var/log/macromilter 22 | LOGFILE_NAME = macromilter.log 23 | # Loglevels are: 1 = Debug (default) , 2 = Info, 3 = Warning/Error 24 | LOGLEVEL = 1 25 | 26 | [Whitelist] 27 | # Add (comma separated json format) some whitelisted recipients or sender to the list to skip the VBA parsing ["xyz@example.de","test@test.de"] 28 | Recipients = ["", ""] 29 | # Add a SHA256 hash from the macro code - to obtain these hash please see in the log for "INFO: [ID] The macro hash is: [..]XYZ[..]" 30 | # example: 05357f85049ba05fb9c7cdc9c6e979b0cb9db600a78eaf98a39344db2f6a6473 31 | # Please define as json: ["hash#1","hash#2"] 32 | Macrohash = [] 33 | -------------------------------------------------------------------------------- /macromilter/install_ubuntu.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # create files and folders 3 | mkdir /etc/macromilter/ 4 | mkdir -p /var/log/macromilter/ 5 | # only needed for a chroot env 6 | # mkdir /var/spool/postfix/etc/milter 7 | 8 | # install macromilter dependencies 9 | apt-get update 10 | apt-get install python2.7 python2.7-dev libmilter-dev libmilter1.0.1 python-pip 11 | 12 | # install oletools 13 | pip install oletools 14 | # install pymilter --> maybe you need some addtional dependencies - see doc 15 | pip install pymilter 16 | # install configparser 17 | pip install configparser 18 | pip install olefile 19 | 20 | # copy the python script 21 | cd /usr/bin/ 22 | wget https://raw.githubusercontent.com/sbidy/MacroMilter/master/macromilter/macromilter.py 23 | cd /etc/macromilter/ 24 | wget https://raw.githubusercontent.com/sbidy/MacroMilter/master/macromilter/config.ini 25 | # setup upstart config 26 | cd /etc/init/ 27 | wget https://raw.githubusercontent.com/sbidy/MacroMilter/master/macromilter/MacroMilter.conf 28 | # if systemd use this 29 | cd /etc/systemd/system/ 30 | wget https://raw.githubusercontent.com/sbidy/MacroMilter/master/macromilter/macromilter.service 31 | # logrotate file 32 | cd /etc/logrotate.d/ 33 | wget https://raw.githubusercontent.com/sbidy/MacroMilter/master/macromilter/macromilter.logrotate 34 | 35 | # Ubuntu 12.04 ami-b08b6cd8: using upstart 36 | # Ubuntu 14.04 ami-a427efcc: using upstart 37 | # Ubuntu 14.10 and younger: using systemd 38 | 39 | # upstart use: 40 | initctl reload-configuration 41 | # systemd use: 42 | systemctl enable macromilter.service 43 | systemctl is-enabled macromilter.service 44 | 45 | # set chown for postfix 46 | chown postfix:postfix -R /etc/macromilter 47 | chown postfix:postfix -R /var/log/macromilter 48 | chown postfix:postfix /usr/bin/macromilter.py 49 | 50 | # only needed if you run the milter at chroot an with a linux-socket 51 | # chown postfix:postfix -R /var/spool/postfix/etc/milter 52 | 53 | # start and check 54 | service MacroMilter start 55 | tail /var/log/syslog 56 | -------------------------------------------------------------------------------- /macromilter/macromilter.logrotate: -------------------------------------------------------------------------------- 1 | /var/log/macromilter/macromilter.log 2 | { 3 | compress 4 | dateext 5 | maxage 1 6 | rotate 14 7 | missingok 8 | notifempty 9 | size +4096k 10 | create 640 postfix postfix 11 | sharedscripts 12 | postrotate 13 | service macromilter restart 14 | endscript 15 | } 16 | 17 | -------------------------------------------------------------------------------- /macromilter/macromilter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ## Macro Milter for postfix - https://github.com/sbidy/MacroMilter 3 | ## 4 | ## 1.4 - 15.12.2015 sbidy - Update comments, add hash-set for lookup and safe to persistent 5 | ## 1.5 - 16.12.2015 sbidy - Add date-time to performance log 6 | ## 1.6 - 16.12.2015 sbidy - Change to TCP Socket, Socket an timeout to global, update name to "MacroMilter", set_exception_policy to ACCEPT, fix timer bug for performance data 7 | ## 1.7 - 05.01.2016 sbidy - Adding Extensionlogging 8 | ## 1.8 - 07.01.2016 sbidy - Commit at github, add the privacy statement 9 | ## 1.9 - 12.01.2016 sbidy - Clean up the code - deleted the virus total function. Hive off to a separate project/milter 10 | ## 2.0 - 12.01.2016 sbidy - Add spam header "X-Spam-Flag" to yes for a non-MIME Message 11 | ## 2.1 - 15.02.2016 sbidy - Fix multi attachment bug, now parses multiple attachments, docm and xlsm added 12 | ## 2.2 - 18.02.2016 sbidy - Fix while loop bug 13 | ## 2.3 - 22.02.2016 sbidy - Fix multiple entry at hashtable and remove ppt 14 | ## 2.4 - 07.03.2016 sbidy - Update bad zip file exception and disable file logging + x-spam-flag 15 | ## 2.5 - 07.03.2016 sbidy - Fix run.log bug and disable connect log 16 | ## 2.6 - 08.03.2016 Gulaschcowboy - Added CFG_DIR, fixed some paths, added systemd.service, Readme.opensuse and logrotate script 17 | ## 2.7 - 18.03.2016 sbidy - Added rtf to file list 18 | ## 2.8 - 29.03.2016 sbidy - Added some major fixes and code cleanup, added the zip extraction for .zip files regarding issue #5 19 | ## 2.8.1 - 30.03.2016 sbidy - Fix the str-exception, added some logging informations 20 | ## 2.9 - 20.05.2016 sbidy - Fix issue #6 - queue not empty after log file can't written, write extension data to file deleted 21 | ## 2.9.1 - 20.05.2016 sbidy - Additional fixes for #6 22 | ## 2.9.2 - 27.06.2016 sbidy - Add changes from heinrichheine and merge to master 23 | ## 2.9.3 - 27.06.2016 heinrichheine/sbidy - Tested and updated version, some fixes added 24 | # -------------------------- V3 ----------------------------------------- 25 | ## 3.0 - 05.01.2017 sbidy - Add some enhancements and major changes, used mraptor from oletools, cleanup and remove the multi-thread feature, add configuration file 26 | ## 3.1 - 10.01.2017 sbidy - Bugfix for whitelist exception 27 | ## 3.2 - 12.01.2017 sbidy - Fix for exceptions.UnboundLocalError, possible fix for #10 zip - extraction did not work properly 28 | ## 3.3 - 05.10.2017 sbidy - Update directory for FHS conformity see #13 29 | ## 3.4 - 27.10.2017 sbidy - Update and fix some bugs #19, #18 and #17 - create release 30 | ## 3.5.1 - 03.01.2018 sbidy - Fix for #31 #27 #29, some updates for the logging and umask 31 | ## 3.5.2 - 04.01.2018 sbidy - update the tempfile handling for more security and some other fixes, re-introduce the UMASK 32 | ## 3.5.3 - 06.02.2018 sbidy - implementing the macro whitelisting, update the config parsing to json 33 | ## 3.6 - 03.01.2018 sbidy - fixing mutliple issues and bugs (#41, #38, #37, #35, #31, #30). Add MIME header for the different stages. 34 | ## 3.6.1 - 25.09.2019 sbidy - update and fixing filehandle exception in tempfile. Added config function to dump the mail body to file (DUMP_BODY) 35 | 36 | # The MIT License (MIT) 37 | 38 | # Copyright (c) 2019 Stephan Traub - audius GmbH, www.audius.de 39 | 40 | # Permission is hereby granted, free of charge, to any person obtaining a copy 41 | # of this software and associated documentation files (the "Software"), to deal 42 | # in the Software without restriction, including without limitation the rights 43 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 44 | # copies of the Software, and to permit persons to whom the Software is 45 | # furnished to do so, subject to the following conditions: 46 | 47 | # The above copyright notice and this permission notice shall be included in all 48 | # copies or substantial portions of the Software. 49 | 50 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 51 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 52 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 53 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 54 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 55 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 56 | # SOFTWARE. 57 | 58 | import Milter 59 | import StringIO 60 | import time 61 | import email 62 | import sys 63 | import re 64 | import hashlib 65 | import os 66 | import errno 67 | import logging 68 | import logging.handlers 69 | import io 70 | import traceback 71 | import tempfile 72 | import shutil 73 | import olefile 74 | import json 75 | 76 | from sets import Set 77 | from oletools import olevba, mraptor 78 | from Milter.utils import parse_addr 79 | from socket import AF_INET6 80 | from ConfigParser import SafeConfigParser 81 | from oletools.olevba import VBA_Parser 82 | 83 | 84 | # use backport if needed 85 | if sys.version_info[0] <= 2: 86 | from zipfile import ZipFile, is_zipfile 87 | else: 88 | # Python 3.x+ 89 | from zipfile import ZipFile, is_zipfile 90 | 91 | ## Config see ./config.ini 92 | __version__ = '3.6.1' # version 93 | 94 | # get the config from FHS conform dir (bug #13) 95 | CONFIG = os.path.join(os.path.dirname("/etc/macromilter/"),"config.ini") 96 | if not os.path.isfile(CONFIG): 97 | CONFIG = os.path.join(os.path.dirname(__file__),"config.ini") 98 | 99 | # get the configuration items 100 | if os.path.isfile(CONFIG): 101 | config = SafeConfigParser() 102 | config.read(CONFIG) 103 | SOCKET = config.get('Milter', 'SOCKET') 104 | try: 105 | UMASK = int(config.get('Milter', 'UMASK'), base=0) 106 | except: 107 | UMASK = 0o0077 108 | TIMEOUT = config.getint('Milter', 'TIMEOUT') 109 | MAX_FILESIZE = config.getint('Milter', 'MAX_FILESIZE') 110 | MESSAGE = config.get('Milter', 'MESSAGE') 111 | MAX_ZIP = config.getint('Milter', 'MAX_ZIP') 112 | REJECT_MESSAGE = config.getboolean('Milter', 'REJECT_MESSAGE') 113 | LOGFILE_DIR = config.get('Logging', 'LOGFILE_DIR') 114 | LOGFILE_NAME = config.get('Logging', 'LOGFILE_NAME') 115 | LOGLEVEL = config.getint('Logging', 'LOGLEVEL') 116 | DUMP_BODY = config.getboolean('Milter', 'DUMP_BODY') 117 | else: 118 | sys.exit("Please check the config file! Config path: %s" % CONFIG) 119 | # ============================================================================= 120 | 121 | LOGFILE_PATH = os.path.join(LOGFILE_DIR, LOGFILE_NAME) 122 | HASHTABLE_PATH = os.path.join(LOGFILE_DIR, "hashtable.db") 123 | 124 | # Config check 125 | if DUMP_BODY is None: 126 | DUMP_BODY = False 127 | 128 | # fallback if a file can't detect by the file magic 129 | EXTENSIONS = ".dot",".doc",".xls",".docm",".dotm",".xlsm",".xlsb",".pptm", ".ppsm", ".rtf", ".mht", ".ppt" 130 | 131 | # Set up a specific logger with our desired output level 132 | log = logging.getLogger('MacroMilter') 133 | # disable logging by default - enable it in main app: 134 | log.setLevel(logging.CRITICAL+1) 135 | 136 | Hash_Whitelist = None 137 | hashtable = None 138 | WhiteList = None 139 | 140 | # Custom exception class for archive bomb exception 141 | class TooManyZipException(Exception): 142 | pass 143 | 144 | ## Customized milter class - partly copied from 145 | ## https://github.com/jmehnle/pymilter/blob/master/milter-template.py 146 | class MacroMilter(Milter.Base): 147 | '''Base class for MacroMilter to move boilerplate connection stuff away from the real 148 | business logic for macro parsing 149 | ''' 150 | def __init__(self): # A new instance with each new connection. 151 | self.id = Milter.uniqueID() # Integer incremented with each call. 152 | self.messageToParse = None 153 | self.level = 0 154 | self.headercount = 0 155 | self.size = 0 156 | 157 | @Milter.noreply 158 | def connect(self, IPname, family, hostaddr): 159 | 160 | # define all vars 161 | self.IP = hostaddr[0] 162 | self.port = hostaddr[1] 163 | if family == AF_INET6: 164 | self.flow = hostaddr[2] 165 | self.scope = hostaddr[3] 166 | else: 167 | self.flow = None 168 | self.scope = None 169 | self.IPname = IPname # Name from a reverse IP lookup 170 | self.messageToParse = None # content 171 | log.debug("[%d] Connect from %s at %s" % (self.id, IPname, hostaddr)) # for logging 172 | return Milter.CONTINUE 173 | 174 | @Milter.noreply 175 | def envfrom(self, mailfrom, *str): 176 | self.recipients = [] # list of recipients 177 | self.messageToParse = StringIO.StringIO() 178 | self.canon_from = '@'.join(parse_addr(mailfrom)) 179 | self.messageToParse.write('From %s %s\n' % (self.canon_from, time.ctime())) 180 | return Milter.CONTINUE 181 | 182 | @Milter.noreply 183 | def envrcpt(self, to, *str): 184 | # remove the < and > 185 | self.recipients.append(to[1:-1]) 186 | return Milter.CONTINUE 187 | 188 | @Milter.noreply 189 | def header(self, header_field, header_field_value): 190 | self.messageToParse.write("%s: %s\n" % (header_field, header_field_value)) 191 | return Milter.CONTINUE 192 | 193 | @Milter.noreply 194 | def eoh(self): 195 | self.messageToParse.write("\n") 196 | return Milter.CONTINUE 197 | 198 | @Milter.noreply 199 | def body(self, chunk): 200 | self.messageToParse.write(chunk) 201 | return Milter.CONTINUE 202 | 203 | def close(self): 204 | # stop timer at close 205 | return Milter.CONTINUE 206 | 207 | def abort(self): 208 | # nothing to clean up 209 | return Milter.CONTINUE 210 | 211 | def eom(self): 212 | '''This method is called when the end of the email message has been reached. 213 | This event also triggers the milter specific actions 214 | ''' 215 | try: 216 | # set data pointer back to 0 217 | self.messageToParse.seek(0) 218 | # use email from package email to parse the message string 219 | msg = email.message_from_string(self.messageToParse.getvalue()) 220 | # Set Reject Message - definition from here 221 | # https://www.iana.org/assignments/smtp-enhanced-status-codes/smtp-enhanced-status-codes.xhtml 222 | self.setreply('550', '5.7.1', MESSAGE) 223 | 224 | if self.sender_is_in_whitelist(): 225 | self.addheader('X-MacroMilter-Status', 'Whitelisted') 226 | return Milter.ACCEPT 227 | else: 228 | return self.checkforVBA(msg) 229 | 230 | except Exception: 231 | exc_type, exc_obj, exc_tb = sys.exc_info() 232 | fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] 233 | log.error("Unexpected error - fall back to ACCEPT: %s %s %s" % (exc_type, fname, exc_tb.tb_lineno)) 234 | self.addheader('X-MacroMilter-Status', 'Unchecked') 235 | return Milter.CONTINUE 236 | 237 | ## ==== Data processing ==== 238 | 239 | def fileHasAlreadyBeenParsed(self, data): 240 | # generate Hash from file 241 | hash_data = hashlib.md5(data).hexdigest() 242 | # check if file is already parsed 243 | if hash_data in hashtable: 244 | log.warning("[%d] Attachment %s already parsed: REJECT" % (self.id, hash_data)) 245 | return True 246 | else: 247 | # check if the attachment is a SHA 248 | hash_data = hashlib.sha256(data).hexdigest() 249 | if hash_data in hashtable: 250 | log.warning("[%d] Attachment %s already parsed: REJECT" % (self.id, hash_data)) 251 | return True 252 | else: 253 | return False 254 | 255 | def addHashtoDB(self, data): 256 | hash_data = hashlib.sha256(data).hexdigest() 257 | hashtable.add(hash_data) 258 | with open(HASHTABLE_PATH, "a") as hashdb: 259 | hashdb.write(hash_data + '\n') 260 | 261 | log.debug("[%d] File added: %s" % (self.id, hash_data)) 262 | 263 | def removeHashFromDB(self, data): 264 | hash_data = hashlib.sha256(data).hexdigest() 265 | hashtable.discard(hash_data) 266 | with open(HASHTABLE_PATH, "r+") as hashdb: 267 | for line in hashdb: 268 | if line != (hash_data + "\n"): 269 | hashdb.write(line) 270 | # legacy MD5 271 | hash_data = hashlib.md5(data).hexdigest() 272 | hashtable.discard(hash_data) 273 | with open(HASHTABLE_PATH, "r+") as hashdb: 274 | for line in hashdb: 275 | if line != (hash_data + "\n"): 276 | hashdb.write(line) 277 | 278 | 279 | def checkforVBA(self, msg): 280 | ''' 281 | Checks if it contains a vba macro and checks if user is whitelisted or file already parsed 282 | ''' 283 | # Accept all messages with no attachment 284 | result = Milter.ACCEPT 285 | # check if the body must be replaced 286 | newbody = False 287 | try: 288 | for part in msg.walk(): 289 | # for name, value in part.items(): 290 | # log.debug(' - %s: %r' % (name, value)) 291 | content_type = part.get_content_type() 292 | log.debug('[%d] Content-Type: %r' % (self.id, content_type)) 293 | # TODO: handle any content-type, but check the file magic? 294 | if not content_type.startswith('multipart'): 295 | filename = part.get_filename(None) 296 | attachment = part.get_payload(decode=True) 297 | 298 | if attachment is None and filename is None: 299 | return Milter.CONTINUE 300 | log.debug('[%d] Analyzing attachment: %r' % (self.id, filename)) 301 | attachment_lowercase = attachment.lower() 302 | attachment_fileobj = StringIO.StringIO(attachment) 303 | 304 | # check if file was already parsed 305 | if self.fileHasAlreadyBeenParsed(attachment): 306 | if DUMP_BODY and not newbody: 307 | self.writeBodyDump(msg) 308 | if REJECT_MESSAGE is False: 309 | part.set_payload('This attachment has been removed because it contains a suspicious macro.') 310 | self.addheader('X-MacroMilter-Status', 'Suspicious macro') 311 | part.set_type('text/plain') 312 | part.replace_header('Content-Transfer-Encoding', '7bit') 313 | newbody = True 314 | else: 315 | return Milter.REJECT 316 | 317 | # check if this is a supported file type (if not, just skip it) 318 | # TODO: this function should be provided by olevba 319 | elif olefile.isOleFile(attachment_fileobj) or is_zipfile(attachment_fileobj) or 'http://schemas.microsoft.com/office/word/2003/wordml' in attachment \ 320 | or ('mime' in attachment_lowercase and 'version' in attachment_lowercase \ 321 | and 'multipart' in attachment_lowercase): 322 | vba_code_all_modules = '' 323 | # check if the attachment is a zip 324 | if not olefile.isOleFile(attachment_fileobj): 325 | extn = (os.path.splitext(filename)[1]).lower() 326 | # skip non archives 327 | if is_zipfile(attachment_fileobj) and not (".docx" in extn or ".xlsx" in extn or ".pptx" in extn): 328 | # extract all file in zip and add 329 | try: 330 | zipvba = self.getZipFiles(attachment, filename) 331 | vba_code_all_modules += zipvba + '\n' 332 | except TooManyZipException: 333 | log.warning("[%d] Attachment %s is reached the max. nested zip count! ZipBomb?: REJECT" % (self.id, filename)) 334 | # rewrite the reject message 335 | self.setreply('550', '5.7.2', "The message contains a suspicious archive and was rejected!") 336 | return Milter.REJECT 337 | 338 | # check the rest of the message 339 | try: 340 | vba_parser = olevba.VBA_Parser(filename='message', data=attachment) 341 | for (subfilename, stream_path, vba_filename, vba_code) in vba_parser.extract_all_macros(): 342 | vba_code_all_modules += vba_code + '\n' 343 | except olevba.FileOpenError: 344 | log.error('[%d] Error while processing the message. Possible encrypted zip detected.' % self.id) 345 | 346 | # check the macro code whitelist 347 | if vba_code_all_modules is not None: 348 | if self.macro_is_in_whitelist(vba_code_all_modules): 349 | # macro code is in whitelist 350 | self.removeHashFromDB(attachment) 351 | self.addheader('X-MacroMilter-Status', 'Whitelisted') 352 | continue 353 | 354 | # run the mraptor 355 | m = mraptor.MacroRaptor(vba_code_all_modules) 356 | m.scan() 357 | # suspicious 358 | if m.autoexec and (m.execute or m.write): 359 | # Add sha256 to the database 360 | self.addHashtoDB(attachment) 361 | # Replace the attachment or reject it 362 | if DUMP_BODY and not newbody: 363 | self.writeBodyDump(msg) 364 | if REJECT_MESSAGE: 365 | log.warning('[%d] The attachment %r contains a suspicious macro: REJECT' % (self.id, filename)) 366 | self.addheader('X-MacroMilter-Status', 'Suspicious macro') 367 | return Milter.REJECT 368 | else: 369 | log.warning('[%d] The attachment %r contains a suspicious macro: replace it with a text file' % (self.id, filename)) 370 | self.addheader('X-MacroMilter-Status', 'Suspicious macro') 371 | part.set_payload('This attachment has been removed because it contains a suspicious macro.') 372 | part.set_type('text/plain') 373 | part.replace_header('Content-Transfer-Encoding', '7bit') 374 | newbody = True 375 | else: 376 | log.debug('[%d] The attachment %r is clean.' % (self.id, filename)) 377 | 378 | except Exception: 379 | log.error('[%d] Error while processing the message' % self.id) 380 | exc_type, exc_value, exc_traceback = sys.exc_info() 381 | lines = traceback.format_exception(exc_type, exc_value, exc_traceback) 382 | exep = ''.join('!! ' + line for line in lines) 383 | log.debug("[%d] Exception code: [%s]" % (self.id, exep)) 384 | 385 | if newbody: 386 | body = self.removeHader(msg) 387 | self.message = io.BytesIO(body) 388 | self.replacebody(body) 389 | log.info('[%d] Message relayed' % self.id) 390 | result = Milter.ACCEPT 391 | return result 392 | 393 | def removeHader(self, msg): 394 | ''' 395 | Removes the header form the MIME mail to prevent a duplication. Retruns the raw body as string. 396 | The preamble for non-MIME clients is removed. Non-MIME clients can't read the attachement. 397 | ''' 398 | # remove header from the body 399 | # ToDo: Get the boundary to determine the line beginning better 400 | body = str(msg) 401 | index = 0 402 | for lines in body.splitlines(): 403 | # regarding the https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html 404 | # in a multipart the first boundary beings with '--' 405 | if lines.startswith("--"): 406 | break 407 | else: 408 | index = index + 1 409 | return '\r\n'.join(body.splitlines()[index:]) 410 | 411 | def getZipFiles(self, attachment, filename): 412 | ''' 413 | Checks a zip for parseable files and extracts all macros 414 | ''' 415 | log.debug("[%d] Found attachment with archive extension - file name: %s" % (self.id, filename)) 416 | vba_code_all_modules = '' 417 | file_object = StringIO.StringIO(attachment) 418 | files_in_zip = self.zipwalk(file_object,0,[]) 419 | 420 | for zip_name, zip_data in files_in_zip: 421 | # checks if it is a file 422 | 423 | zip_mem_data = StringIO.StringIO(zip_data) 424 | name, ext = os.path.splitext(zip_name.filename) 425 | # send to the VBA_Parser 426 | # fallback with extensions - maybe removed in future releases 427 | if olefile.isOleFile(zip_mem_data) or ext in EXTENSIONS: 428 | log.info("[%d] File in zip detected! Name: %s - check for VBA" % (self.id, zip_name.filename)) 429 | vba_parser = olevba.VBA_Parser(filename=zip_name.filename, data=zip_data) 430 | for (subfilename, stream_path, vba_filename, vba_code) in vba_parser.extract_all_macros(): 431 | vba_code_all_modules += vba_code + '\n' 432 | return vba_code_all_modules 433 | 434 | def sender_is_in_whitelist(self): 435 | ''' 436 | Lookup if the sender is at the whitelist 437 | ''' 438 | global WhiteList 439 | msg_from = self.canon_from 440 | msg_to = self.recipients 441 | 442 | # return if not RFC char detected 443 | if "\'" in msg_from: 444 | return False 445 | if "\'" in msg_to: 446 | return False 447 | 448 | if WhiteList is not None: 449 | log.debug("[%d] Whitelist compare: %s = %s" % (self.id, msg_from, msg_to)) 450 | # check if it is a list 451 | for name in WhiteList: 452 | if name not in (None, ''): 453 | if any(name in s for s in msg_from): 454 | log.info("Whitelisted user %s - accept all attachments" % (msg_from)) 455 | return True 456 | if any(name in s for s in msg_to): 457 | log.info("Whitelisted user %s - accept all attachments" % (msg_to)) 458 | return True 459 | return False 460 | 461 | def macro_is_in_whitelist(self, vbastring): 462 | ''' 463 | Lookup for macro hash in whitelist 464 | ''' 465 | global Hash_Whitelist 466 | # create hex over macro code 467 | vba_hex = vbastring.encode("hex") 468 | # generate sha256 hash 469 | vba_hash = hashlib.sha256(vba_hex).hexdigest() 470 | log.info("[%d] The macro hash is: %s" % (self.id, vba_hash)) 471 | 472 | if Hash_Whitelist is not None: 473 | for hash in Hash_Whitelist: 474 | if hash in vba_hash: 475 | log.info("[%d] Whitelisted macro code %s - accept attachment" % (self.id, vba_hash)) 476 | return True 477 | return False 478 | 479 | ## === Support Functions === 480 | ''' 481 | Walks through the zip file and extracts all data for VBA scanning 482 | :return: File content generator 483 | ''' 484 | def zipwalk(self, zfilename, count, tmpfiles): 485 | 486 | z = ZipFile(zfilename,'r') 487 | # start walk 488 | for info in z.infolist(): 489 | # 35 490 | is_encrypted = info.flag_bits & 0x1 491 | if not is_encrypted: 492 | fname = info.filename 493 | data = z.read(fname) 494 | extn = (os.path.splitext(fname)[1]).lower() 495 | 496 | # create a random secure temp file 497 | tmp_fs, tmpfpath = tempfile.mkstemp(suffix=extn) 498 | # add tmp filename to list 499 | tmpfiles.append(tmpfpath) 500 | 501 | if extn=='.zip' or extn=='.7z': 502 | checkz=False 503 | # use the file descriptor to write to the file 504 | tmpfile = os.fdopen(tmp_fs, "w") 505 | tmpfile.write(data) 506 | # Close the file to avoid the open file exception 507 | tmpfile.close() 508 | 509 | if is_zipfile(tmpfpath): 510 | checkz=True 511 | count = count+1 512 | # check each round 513 | if count > MAX_ZIP: 514 | self.deleteFileRecursive(tmpfiles) 515 | tmpfiles = [] 516 | raise ToManyZipException("[%d] Too many nested zips found - possible zipbomb!" % self.id) 517 | if checkz and not olefile.isOleFile(data): 518 | try: 519 | # recursive call if nested 520 | for x in self.zipwalk(tmpfpath, count, tmpfiles): 521 | yield x 522 | except Exception: 523 | self.deleteFileRecursive(tmpfiles) 524 | tmpfiles = [] 525 | raise 526 | else: 527 | # close filehandler if not used 528 | os.close(tmp_fs) 529 | # return the generator 530 | yield (info, data) 531 | # cleanup tmp 532 | self.deleteFileRecursive(tmpfiles) 533 | tmpfiles = [] 534 | 535 | ''' 536 | Delete the files recursive from the tmp folder 537 | ''' 538 | def deleteFileRecursive(self, filelist): 539 | for sfile in filelist: 540 | try: 541 | os.remove(sfile) 542 | log.debug("[%d] File %s removed from tmp folder" % (self.id, sfile)) 543 | except OSError: 544 | pass 545 | 546 | ''' 547 | Write the dump of the mail body to the logging folder 548 | ''' 549 | def writeBodyDump(self, msg): 550 | try: 551 | dumpname = time.strftime("%Y%m%d-%H%M%S") 552 | dumpname = "Dump_"+dumpname+"_"+str(self.id) 553 | dumpfile = os.path.join(LOGFILE_DIR, dumpname) 554 | with open(dumpfile, 'w') as f: 555 | f.write(str(msg)) 556 | f.close() 557 | log.info("Body dump written in %s" % dumpfile) 558 | except Exception: 559 | log.error("Cant write body dump") 560 | 561 | ## ===== END CLASS ======== 562 | 563 | ## ==== start MAIN ======== 564 | 565 | def WhiteListLoad(): 566 | ''' 567 | Function to load the data from the WhiteList file and load into memory 568 | ''' 569 | global WhiteList, Hash_Whitelist 570 | WhiteList = json.loads(config.get("Whitelist", "Recipients")) 571 | Hash_Whitelist = json.loads(config.get("Whitelist", "Macrohash")) 572 | 573 | def HashTableLoad(): 574 | ''' 575 | Load the hash info from file to memory 576 | ''' 577 | # Load hashes from file 578 | global hashtable 579 | oldumask = os.umask(0o0026) 580 | hashtable = set(line.strip() for line in open(HASHTABLE_PATH, 'a+')) 581 | os.umask(oldumask) 582 | 583 | def main(): 584 | 585 | # make sure the log directory exists: 586 | try: 587 | os.makedirs(LOGFILE_DIR,0o0027) 588 | except: 589 | pass 590 | 591 | # Load the whitelist into memory 592 | WhiteListLoad() 593 | HashTableLoad() 594 | # Add the log message handler to the logger 595 | # rotation handled by logrotatd 596 | oldumask = os.umask(0o0026) 597 | handler = logging.handlers.WatchedFileHandler(LOGFILE_PATH, encoding='utf8') 598 | # create formatter and add it to the handlers 599 | formatter = logging.Formatter('%(asctime)s - %(levelname)8s: %(message)s') 600 | handler.setFormatter(formatter) 601 | log.addHandler(handler) 602 | os.umask(oldumask) 603 | 604 | # Loglevels are: 1 = Debug, 2 = Info, 3 = Error 605 | 606 | if LOGLEVEL == 2: 607 | log.setLevel(logging.INFO) 608 | elif LOGLEVEL == 3: 609 | log.setLevel(logging.WARNING) 610 | else: 611 | log.setLevel(logging.DEBUG) 612 | 613 | # Register to have the Milter factory create instances of the class: 614 | Milter.factory = MacroMilter 615 | flags = Milter.CHGBODY + Milter.CHGHDRS + Milter.ADDHDRS 616 | flags += Milter.ADDRCPT 617 | flags += Milter.DELRCPT 618 | Milter.set_flags(flags) # tell Sendmail which features we use 619 | 620 | # start milter processing 621 | print("%s MacroMilter startup - Version %s" % (time.strftime('%Y-%m-%d %H:%M:%S'), __version__ )) 622 | print('logging to file %s' % LOGFILE_PATH) 623 | 624 | log.info('Starting MarcoMilter v%s - listening on %s' % (__version__, SOCKET)) 625 | log.debug('Python version: %s' % sys.version) 626 | sys.stdout.flush() 627 | 628 | # ensure desired permissions on unix socket 629 | os.umask(UMASK) 630 | 631 | # set the "last" fall back to ACCEPT if exception occur 632 | Milter.set_exception_policy(Milter.ACCEPT) 633 | 634 | # start the milter 635 | Milter.runmilter("MacroMilter", SOCKET, TIMEOUT) 636 | 637 | print("%s MacroMilter shutdown" % time.strftime('%Y-%m-%d %H:%M:%S')) 638 | 639 | if __name__ == "__main__": 640 | main() 641 | -------------------------------------------------------------------------------- /macromilter/macromilter.service: -------------------------------------------------------------------------------- 1 | 2 | # Description: 3 | # 4 | # Used to start MacroMilter service 5 | # 6 | 7 | [Unit] 8 | Description=MacroMilter Service 9 | Requires=var-run.mount 10 | Wants=nss-lookup.target network.target remote-fs.target time-sync.target 11 | After=var-run.mount nss-lookup.target network.target remote-fs.target time-sync.target 12 | Before=mail-transfer-agent.target 13 | 14 | [Service] 15 | #Type=forking 16 | User=postfix 17 | Group=postfix 18 | #RootDirectory=/etc/macromilter/ 19 | ExecStart=/usr/bin/python /etc/macromilter/macromilter.py 20 | Restart=always 21 | RestartSec=1 22 | 23 | [Install] 24 | WantedBy=multi-user.target 25 | 26 | -------------------------------------------------------------------------------- /macromilter/update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd /usr/bin/ 3 | rm macromilter.py 4 | wget https://raw.githubusercontent.com/sbidy/MacroMilter/master/MacroMilter/macromilter.py 5 | service MacroMilter restart 6 | 7 | -------------------------------------------------------------------------------- /test_mails/01_mail_without_attachment.eml: -------------------------------------------------------------------------------- 1 | Return-Path: 2 | Delivered-To: 3 | Received: from myhost.server.de 4 | by myhost.server.de (Dovecot) with LMTP id a3h2342rfwsVdaZgAAnDPNtw 5 | for ; Sat, 07 May 2016 15:07:09 +0200 6 | Received: from 190-93-98-224.rev.greendottt.net (190-93-98-224.rev.greendottt.net [190.93.98.224]) 7 | by myhost.server.de (Postfix) with ESMTP id 4D334KD0290 8 | for ; Sat, 7 May 2016 15:07:07 +0200 (CEST) 9 | Message-ID: <379405337244066269900807@myhost.server.de> 10 | From: 11 | To: 12 | Subject: =?utf-8?B?V2lsbHN0IGR1IGVpbmUgaGVpw59lIE5hY2h0Pw==?= 13 | Date: 6 May 2016 22:01:54 -0800 14 | MIME-Version: 1.0 15 | Content-type: multipart/alternative; 16 | boundary="---C78133B8B3750A387ECC474C8AF5C781" 17 | X-Mailer: Jkanho pfdxwm 18 | X-Virus-Scanned: clamav-milter 0.98.7 at myhost.server.de 19 | X-Virus-Status: Clean 20 | X-Spam-Flag: YES 21 | X-Spam-Status: Yes, score=9.7 required=7.7 tests=BAYES_00,DATE_IN_PAST_06_12, 22 | HTML_MESSAGE,TVD_RCVD_IP,UNPARSEABLE_RELAY,URIBL_ABUSE_SURBL,URIBL_BLOCKED, 23 | URIBL_CR_SURBL,URIBL_DBL_SPAM,URI_WP_DIRINDEX,URI_WP_HACKED_2 autolearn=no 24 | version=3.3.2 25 | X-Spam-Report: 26 | * 0.0 TVD_RCVD_IP Message was received from an IP address 27 | * 1.5 DATE_IN_PAST_06_12 Date: is 6 to 12 hours before Received: date 28 | * -1.9 BAYES_00 BODY: Bayes spam probability is 0 to 1% 29 | * [score: 0.0000] 30 | * 0.0 HTML_MESSAGE BODY: HTML included in message 31 | * 2.5 URIBL_DBL_SPAM Contains a spam URL listed in the DBL blocklist 32 | * [URIs: reijureiki.com] 33 | * 1.3 URIBL_CR_SURBL Contains an URL listed in the CR SURBL blocklist 34 | * [URIs: reijureiki.com] 35 | * 1.2 URIBL_ABUSE_SURBL Contains an URL listed in the ABUSE SURBL 36 | * blocklist 37 | * [URIs: reijureiki.com] 38 | * 0.0 URIBL_BLOCKED ADMINISTRATOR NOTICE: The query to URIBL was blocked. 39 | * See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block 40 | * for more information. 41 | * [URIs: reijureiki.com] 42 | * 3.0 URI_WP_DIRINDEX URI for compromised WordPress site, possible malware 43 | * 0.0 UNPARSEABLE_RELAY Informational: message has unparseable relay lines 44 | * 2.0 URI_WP_HACKED_2 URI for compromised WordPress site, possible malware 45 | X-Spam-Level: ********* 46 | X-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on myhost.server.de 47 | 48 | This is a multi-part message in MIME format. 49 | -----C78133B8B3750A387ECC474C8AF5C781 50 | Content-type: text/plain; 51 | charset="cp-850" 52 | Content-transfer-encoding: quoted-printable 53 | 54 | Bist du verfügbar? 55 | Ich suche nach einem Quickie heute Abend ... 56 | 57 | Ich bin 21 und meine Pu$$y will S#x! 58 | 59 | Hallo, mein Benutzername Viktori 60 | Meinem Profil ist=20 61 | hier 62 | -----C78133B8B3750A387ECC474C8AF5C781 63 | Content-type: text/html; 64 | charset="cp-850" 65 | Content-transfer-encoding: quoted-printable 66 | 67 | 69 | Bist du verfügbar?
70 | Ich suche nach einem Quickie heute Abend ...
71 |
72 | Ich bin 21 und meine Pu$$y will S#x!
73 |
74 | Hallo, mein Benutzername Viktori
75 | Meinem Profil ist <= 77 | b> hier 78 | 79 | -----C78133B8B3750A387ECC474C8AF5C781-- 80 | 81 | 82 | -------------------------------------------------------------------------------- /test_mails/02_mail_with_infected_word_document.eml: -------------------------------------------------------------------------------- 1 | Return-Path: 2 | Delivered-To: 3 | Received: from myhost.server.com 4 | by myhost.server.com (Dovecot) with LMTP id xgh83hdlskfhwAAnDPNtw 5 | for ; Fri, 06 May 2016 12:46:30 +0200 6 | Received: from cable-178-148-132-143.dynamic.sbb.rs (cable-178-148-132-143.dynamic.sbb.rs [178.148.132.143]) 7 | by myhost.server.com (Postfix) with ESMTP id 837HD804C2 8 | for ; Fri, 6 May 2016 12:46:29 +0200 (CEST) 9 | Date: Fri, 06 May 2016 12:46:29 +0200 10 | To: "root@myhost.server.com" 11 | From: Craig Dawson 12 | Subject: Re: 13 | Message-ID: <11162134532523c082c8e459be9dccc04d@sbb.rs> 14 | X-Priority: 3 15 | X-Mailer: PHPMailer [version 1.73] 16 | MIME-Version: 1.0 17 | Content-Type: multipart/mixed; 18 | boundary="b1_111612a42c23c082c8e459be9dccc04d" 19 | X-Virus-Scanned: clamav-milter 0.98.7 at myhost.server.com 20 | X-Virus-Status: Clean 21 | X-Spam-Status: No, score=0.1 required=7.7 tests=BAYES_00,HTML_MESSAGE, 22 | UNPARSEABLE_RELAY,XPRIO autolearn=no version=3.3.2 23 | X-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on myhost.server.com 24 | 25 | --b1_111612a42c23c082c8e459be9dccc04d 26 | Content-Type: multipart/alternative; 27 | boundary="b2_111612a42c23c082c8e459be9dccc04d" 28 | 29 | --b2_111612a42c23c082c8e459be9dccc04d 30 | Content-Type: text/plain; charset = "iso-8859-1" 31 | Content-Transfer-Encoding: 8bit 32 | 33 | Good evening root, 34 | 35 | As promised, I have attached the spreadsheet contains last 50 transactionand your account actual balance. 36 | 37 | Regards, 38 | 39 | Craig Dawson 40 | 41 | --b2_111612a42c23c082c8e459be9dccc04d 42 | Content-Type: text/html; charset = "iso-8859-1" 43 | Content-Transfer-Encoding: 8bit 44 | 45 | 46 | 47 | 48 | 49 |

Good evening root,

50 |


As promised, I have attached the spreadsheet contains last 50 transaction 51 | and your account actual balance.

52 |

Regards,

Craig Dawson

53 | 54 | 55 | 56 | 57 | 58 | --b2_111612a42c23c082c8e459be9dccc04d-- 59 | --b1_111612a42c23c082c8e459be9dccc04d 60 | Content-Type: application/msword; name="rechnung_-postmaster 789.doc" 61 | Content-Transfer-Encoding: base64 62 | Content-Disposition: attachment; filename="rechnung_-postmaster 789.doc" 63 | 64 | PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9InllcyI/Pg0K 65 | PD9tc28tYXBwbGljYXRpb24gcHJvZ2lkPSJXb3JkLkRvY3VtZW50Ij8+DQo8dzp3b3JkRG9jdW1l 66 | bnQgeG1sbnM6dz0iaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS9vZmZpY2Uvd29yZC8yMDAz 67 | L3dvcmRtbCIgeG1sbnM6dj0idXJuOnNjaGVtYXMtbWljcm9zb2Z0LWNvbTp2bWwiIHhtbG5zOncx 68 | MD0idXJuOnNjaGVtYXMtbWljcm9zb2Z0LWNvbTpvZmZpY2U6d29yZCIgeG1sbnM6c2w9Imh0dHA6 69 | Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vc2NoZW1hTGlicmFyeS8yMDAzL2NvcmUiIHhtbG5zOmFt 70 | bD0iaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS9hbWwvMjAwMS9jb3JlIiB4bWxuczp3eD0i 71 | aHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS9vZmZpY2Uvd29yZC8yMDAzL2F1eEhpbnQiIHht 72 | bG5zOm89InVybjpzY2hlbWFzLW1pY3Jvc29mdC1jb206b2ZmaWNlOm9mZmljZSIgeG1sbnM6ZHQ9 73 | InV1aWQ6QzJGNDEwMTAtNjVCMy0xMWQxLUEyOUYtMDBBQTAwQzE0ODgyIiB4bWxuczp3c3A9Imh0 74 | dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vb2ZmaWNlL3dvcmQvMjAwMy93b3JkbWwvc3AyIiB3 75 | Om1hY3Jvc1ByZXNlbnQ9Im5vIiB3OmVtYmVkZGVkT2JqUHJlc2VudD0ibm8iIHc6b2N4UHJlc2Vu 76 | dD0ibm8iIHhtbDpzcGFjZT0icHJlc2VydmUiPjx3Omlnbm9yZUVsZW1lbnRzIHc6dmFsPSJodHRw 77 | Oi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL29mZmljZS93b3JkLzIwMDMvd29yZG1sL3NwMiIvPjxv 78 | OkRvY3VtZW50UHJvcGVydGllcz48bzpUaXRsZT4gICAgPC9vOlRpdGxlPjxvOkF1dGhvcj5nZmRj 79 | ZHNjZHNjY2M8L286QXV0aG9yPjxvOkxhc3RBdXRob3I+0L/QsNCy0YPQstCw0YvQstCwPC9vOkxh 80 | c3RBdXRob3I+PG86UmV2aXNpb24+MzwvbzpSZXZpc2lvbj48bzpUb3RhbFRpbWU+NTwvbzpUb3Rh 81 | bFRpbWU+PG86Q3JlYXRlZD4yMDE2LTAzLTE3VDA4OjAzOjAwWjwvbzpDcmVhdGVkPjxvOkxhc3RT 82 | YXZlZD4yMDE2LTAzLTE3VDA4OjIwOjAwWjwvbzpMYXN0U2F2ZWQ+PG86UGFnZXM+MTwvbzpQYWdl 83 | cz48bzpXb3Jkcz4xPC9vOldvcmRzPjxvOkNoYXJhY3RlcnM+MTI8L286Q2hhcmFjdGVycz48bzpD 84 | b21wYW55PnNkZmRzZjwvbzpDb21wYW55PjxvOkxpbmVzPjE8L286TGluZXM+PG86UGFyYWdyYXBo 85 | cz4xPC9vOlBhcmFncmFwaHM+PG86Q2hhcmFjdGVyc1dpdGhTcGFjZXM+MTI8L286Q2hhcmFjdGVy 86 | c1dpdGhTcGFjZXM+PG86VmVyc2lvbj4xMS4wMDAwPC9vOlZlcnNpb24+PC9vOkRvY3VtZW50UHJv 87 | cGVydGllcz48dzpmb250cz48dzpkZWZhdWx0Rm9udHMgdzphc2NpaT0iVGltZXMgTmV3IFJvbWFu 88 | IiB3OmZhcmVhc3Q9IlRpbWVzIE5ldyBSb21hbiIgdzpoLWFuc2k9IlRpbWVzIE5ldyBSb21hbiIg 89 | dzpjcz0iVGltZXMgTmV3IFJvbWFuIi8+PC93OmZvbnRzPjx3OnN0eWxlcz48dzp2ZXJzaW9uT2ZC 90 | dWlsdEluU3R5bGVuYW1lcyB3OnZhbD0iNCIvPjx3OmxhdGVudFN0eWxlcyB3OmRlZkxvY2tlZFN0 91 | YXRlPSJvZmYiIHc6bGF0ZW50U3R5bGVDb3VudD0iMTU2Ii8+PHc6c3R5bGUgdzp0eXBlPSJwYXJh 92 | Z3JhcGgiIHc6ZGVmYXVsdD0ib24iIHc6c3R5bGVJZD0iYSI+PHc6bmFtZSB3OnZhbD0iTm9ybWFs 93 | Ii8+PHd4OnVpTmFtZSB3eDp2YWw9ItCe0LHRi9GH0L3Ri9C5Ii8+PHc6cnNpZCB3OnZhbD0iMDBB 94 | MDQ3MUQiLz48dzpyUHI+PHd4OmZvbnQgd3g6dmFsPSJUaW1lcyBOZXcgUm9tYW4iLz48dzpzeiB3 95 | OnZhbD0iMjQiLz48dzpzei1jcyB3OnZhbD0iMjQiLz48dzpsYW5nIHc6dmFsPSJSVSIgdzpmYXJl 96 | YXN0PSJSVSIgdzpiaWRpPSJBUi1TQSIvPjwvdzpyUHI+PC93OnN0eWxlPjx3OnN0eWxlIHc6dHlw 97 | ZT0iY2hhcmFjdGVyIiB3OmRlZmF1bHQ9Im9uIiB3OnN0eWxlSWQ9ImEwIj48dzpuYW1lIHc6dmFs 98 | PSJEZWZhdWx0IFBhcmFncmFwaCBGb250Ii8+PHd4OnVpTmFtZSB3eDp2YWw9ItCe0YHQvdC+0LLQ 99 | vdC+0Lkg0YjRgNC40YTRgiDQsNCx0LfQsNGG0LAiLz48dzpzZW1pSGlkZGVuLz48L3c6c3R5bGU+ 100 | PHc6c3R5bGUgdzp0eXBlPSJ0YWJsZSIgdzpkZWZhdWx0PSJvbiIgdzpzdHlsZUlkPSJhMSI+PHc6 101 | bmFtZSB3OnZhbD0iTm9ybWFsIFRhYmxlIi8+PHd4OnVpTmFtZSB3eDp2YWw9ItCe0LHRi9GH0L3Q 102 | sNGPINGC0LDQsdC70LjRhtCwIi8+PHc6c2VtaUhpZGRlbi8+PHc6clByPjx3eDpmb250IHd4OnZh 103 | bD0iVGltZXMgTmV3IFJvbWFuIi8+PC93OnJQcj48dzp0YmxQcj48dzp0YmxJbmQgdzp3PSIwIiB3 104 | OnR5cGU9ImR4YSIvPjx3OnRibENlbGxNYXI+PHc6dG9wIHc6dz0iMCIgdzp0eXBlPSJkeGEiLz48 105 | dzpsZWZ0IHc6dz0iMTA4IiB3OnR5cGU9ImR4YSIvPjx3OmJvdHRvbSB3Onc9IjAiIHc6dHlwZT0i 106 | ZHhhIi8+PHc6cmlnaHQgdzp3PSIxMDgiIHc6dHlwZT0iZHhhIi8+PC93OnRibENlbGxNYXI+PC93 107 | OnRibFByPjwvdzpzdHlsZT48dzpzdHlsZSB3OnR5cGU9Imxpc3QiIHc6ZGVmYXVsdD0ib24iIHc6 108 | c3R5bGVJZD0iYTIiPjx3Om5hbWUgdzp2YWw9Ik5vIExpc3QiLz48d3g6dWlOYW1lIHd4OnZhbD0i 109 | 0J3QtdGCINGB0L/QuNGB0LrQsCIvPjx3OnNlbWlIaWRkZW4vPjwvdzpzdHlsZT48dzpzdHlsZSB3 110 | OnR5cGU9InRhYmxlIiB3OnN0eWxlSWQ9ImhnZmRjZHNjY2NUYWJsZSI+PHc6bmFtZSB3OnZhbD0i 111 | aGdmZGNkc2NjYyBUYWJsZSIvPjx3OnNlbWlIaWRkZW4vPjx3OnJzaWQgdzp2YWw9IjAwQTA0NzFE 112 | Ii8+PHc6clByPjx3eDpmb250IHd4OnZhbD0iVGltZXMgTmV3IFJvbWFuIi8+PC93OnJQcj48dzp0 113 | YmxQcj48dzp0YmxJbmQgdzp3PSIwIiB3OnR5cGU9ImR4YSIvPjx3OnRibENlbGxNYXI+PHc6dG9w 114 | IHc6dz0iMCIgdzp0eXBlPSJkeGEiLz48dzpsZWZ0IHc6dz0iMTA4IiB3OnR5cGU9ImR4YSIvPjx3 115 | OmJvdHRvbSB3Onc9IjAiIHc6dHlwZT0iZHhhIi8+PHc6cmlnaHQgdzp3PSIxMDgiIHc6dHlwZT0i 116 | ZHhhIi8+PC93OnRibENlbGxNYXI+PC93OnRibFByPjwvdzpzdHlsZT48L3c6c3R5bGVzPjx3OmRv 117 | Y1ByPjx3OnZpZXcgdzp2YWw9InByaW50Ii8+PHc6em9vbSB3OnBlcmNlbnQ9IjEwMCIvPjx3OnBy 118 | b29mU3RhdGUgdzpzcGVsbGluZz0iY2xlYW4iIHc6Z3JhbW1hcj0iY2xlYW4iLz48dzpmb3Jtc0Rl 119 | c2lnbi8+PHc6YXR0YWNoZWRUZW1wbGF0ZSB3OnZhbD0iIi8+PHc6ZGVmYXVsdFRhYlN0b3Agdzp2 120 | YWw9IjcwOCIvPjx3OnB1bmN0dWF0aW9uS2VybmluZy8+PHc6Y2hhcmFjdGVyU3BhY2luZ0NvbnRy 121 | b2wgdzp2YWw9IkRvbnRDb21wcmVzcyIvPjx3Om9wdGltaXplRm9yQnJvd3Nlci8+PHc6cmVseU9u 122 | Vk1MLz48dzphbGxvd1BORy8+PHc6dmFsaWRhdGVBZ2FpbnN0U2NoZW1hLz48dzpzYXZlSW52YWxp 123 | ZFhNTCB3OnZhbD0ib2ZmIi8+PHc6aWdub3JlTWl4ZWRDb250ZW50IHc6dmFsPSJvZmYiLz48dzph 124 | bHdheXNTaG93UGxhY2Vob2xkZXJUZXh0IHc6dmFsPSJvZmYiLz48dzpjb21wYXQ+PHc6YnJlYWtX 125 | cmFwcGVkVGFibGVzLz48dzpzbmFwVG9HcmlkSW5DZWxsLz48dzp3cmFwVGV4dFdpdGhQdW5jdC8+ 126 | PHc6dXNlQXNpYW5CcmVha1J1bGVzLz48dzpkb250R3Jvd0F1dG9maXQvPjwvdzpjb21wYXQ+PHdz 127 | cDpyc2lkcz48d3NwOnJzaWRSb290IHdzcDp2YWw9IjAwMEQ0N0MwIi8+PHdzcDpyc2lkIHdzcDp2 128 | YWw9IjAwMEQ0N0MwIi8+PHdzcDpyc2lkIHdzcDp2YWw9IjAwMUM1N0E0Ii8+PHdzcDpyc2lkIHdz 129 | cDp2YWw9IjAwNTc2ODUxIi8+PHdzcDpyc2lkIHdzcDp2YWw9IjAwNjk0RDg5Ii8+PHdzcDpyc2lk 130 | IHdzcDp2YWw9IjAwNzU3Qjg2Ii8+PHdzcDpyc2lkIHdzcDp2YWw9IjAwNzgwMTk5Ii8+PHdzcDpy 131 | c2lkIHdzcDp2YWw9IjAwODQ2QTMxIi8+PHdzcDpyc2lkIHdzcDp2YWw9IjAwQTA0NzFEIi8+PHdz 132 | cDpyc2lkIHdzcDp2YWw9IjAwQ0Y3RTFEIi8+PC93c3A6cnNpZHM+PC93OmRvY1ByPjx3OmJvZHk+ 133 | PHd4OnNlY3Q+PHc6cCB3c3A6cnNpZFI9IjAwMEQ0N0MwIiB3c3A6cnNpZFJEZWZhdWx0PSIwMDBE 134 | NDdDMCI+PHc6cj48dzp0PiAgICA8L3c6dD48L3c6cj48L3c6cD48dzpwIHdzcDpyc2lkUj0iMDAw 135 | RDQ3QzAiIHdzcDpyc2lkUkRlZmF1bHQ9IjAwMEQ0N0MwIi8+PHc6cCB3c3A6cnNpZFI9IjAwMEQ0 136 | N0MwIiB3c3A6cnNpZFJEZWZhdWx0PSIwMDBENDdDMCIvPjx3OnAgd3NwOnJzaWRSPSIwMDBENDdD 137 | MCIgd3NwOnJzaWRSRGVmYXVsdD0iMDAwRDQ3QzAiLz48dzpwIHdzcDpyc2lkUj0iMDAwRDQ3QzAi 138 | IHdzcDpyc2lkUkRlZmF1bHQ9IjAwMEQ0N0MwIi8+PHc6cCB3c3A6cnNpZFI9IjAwMEQ0N0MwIiB3 139 | c3A6cnNpZFJEZWZhdWx0PSIwMDBENDdDMCI+PHc6cj48dzp0PiAgIDwvdzp0PjwvdzpyPjwvdzpw 140 | Pjx3OnNlY3RQciB3c3A6cnNpZFI9IjAwMEQ0N0MwIiB3c3A6cnNpZFNlY3Q9IjAwQTA0NzFEIj48 141 | dzpwZ1N6IHc6dz0iMTE5MDYiIHc6aD0iMTY4MzgiLz48dzpwZ01hciB3OnRvcD0iMTEzNCIgdzpy 142 | aWdodD0iODUwIiB3OmJvdHRvbT0iMTEzNCIgdzpsZWZ0PSIxNzAxIiB3OmhlYWRlcj0iNzA4IiB3 143 | OmZvb3Rlcj0iNzA4IiB3Omd1dHRlcj0iMCIvPjx3OmNvbHMgdzpzcGFjZT0iNzA4Ii8+PHc6ZG9j 144 | R3JpZCB3OmxpbmUtcGl0Y2g9IjM2MCIvPjwvdzpzZWN0UHI+PC93eDpzZWN0Pjwvdzpib2R5Pjwv 145 | dzp3b3JkRG9jdW1lbnQ+ 146 | --b1_111612a42c23c082c8e459be9dccc04d-- 147 | -------------------------------------------------------------------------------- /test_mails/04_mail_with_infected_word_and_clean_zip.eml: -------------------------------------------------------------------------------- 1 | Return-Path: 2 | Delivered-To: 3 | Received: from myhost.server.com 4 | by myhost.server.com (Dovecot) with LMTP id xgh83hdlskfhwAAnDPNtw 5 | for ; Fri, 06 May 2016 12:46:30 +0200 6 | Received: from cable-178-148-132-143.dynamic.sbb.rs (cable-178-148-132-143.dynamic.sbb.rs [178.148.132.143]) 7 | by myhost.server.com (Postfix) with ESMTP id 837HD804C2 8 | for ; Fri, 6 May 2016 12:46:29 +0200 (CEST) 9 | Date: Fri, 06 May 2016 12:46:29 +0200 10 | To: "root@myhost.server.com" 11 | From: Craig Dawson 12 | Subject: Re: 13 | Message-ID: <11162134532523c082c8e459be9dccc04d@sbb.rs> 14 | X-Priority: 3 15 | X-Mailer: PHPMailer [version 1.73] 16 | MIME-Version: 1.0 17 | Content-Type: multipart/mixed; 18 | boundary="----=_Part_154340_179928730.1460022912314" 19 | X-Virus-Scanned: clamav-milter 0.98.7 at myhost.server.com 20 | X-Virus-Status: Clean 21 | X-Spam-Status: No, score=0.1 required=7.7 tests=BAYES_00,HTML_MESSAGE, 22 | UNPARSEABLE_RELAY,XPRIO autolearn=no version=3.3.2 23 | X-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on myhost.server.com 24 | 25 | ------=_Part_154340_179928730.1460022912314 26 | Content-Type: text/plain; charset=UTF-8 27 | Content-Transfer-Encoding: quoted-printable 28 | 29 | Good evening root, 30 | 31 | As promised, I have attached the spreadsheet contains last 50 transactionand your account actual balance. 32 | 33 | Regards, 34 | 35 | Craig Dawson 36 | 37 | ------=_Part_154340_179928730.1460022912314 38 | Content-Type: application/msword; name="rechnung-postmaster789.doc" 39 | Content-Transfer-Encoding: base64 40 | Content-Disposition: attachment; filename="rechnung-postmaster789.doc" 41 | Content-Description: "rechnung-postmaster789.doc" 42 | 43 | PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9InllcyI/Pg0K 44 | PD9tc28tYXBwbGljYXRpb24gcHJvZ2lkPSJXb3JkLkRvY3VtZW50Ij8+DQo8dzp3b3JkRG9jdW1l 45 | bnQgeG1sbnM6dz0iaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS9vZmZpY2Uvd29yZC8yMDAz 46 | L3dvcmRtbCIgeG1sbnM6dj0idXJuOnNjaGVtYXMtbWljcm9zb2Z0LWNvbTp2bWwiIHhtbG5zOncx 47 | MD0idXJuOnNjaGVtYXMtbWljcm9zb2Z0LWNvbTpvZmZpY2U6d29yZCIgeG1sbnM6c2w9Imh0dHA6 48 | Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vc2NoZW1hTGlicmFyeS8yMDAzL2NvcmUiIHhtbG5zOmFt 49 | bD0iaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS9hbWwvMjAwMS9jb3JlIiB4bWxuczp3eD0i 50 | aHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS9vZmZpY2Uvd29yZC8yMDAzL2F1eEhpbnQiIHht 51 | bG5zOm89InVybjpzY2hlbWFzLW1pY3Jvc29mdC1jb206b2ZmaWNlOm9mZmljZSIgeG1sbnM6ZHQ9 52 | InV1aWQ6QzJGNDEwMTAtNjVCMy0xMWQxLUEyOUYtMDBBQTAwQzE0ODgyIiB4bWxuczp3c3A9Imh0 53 | dHA6Ly9zY2hlbWFzLm1pY3Jvc29mdC5jb20vb2ZmaWNlL3dvcmQvMjAwMy93b3JkbWwvc3AyIiB3 54 | Om1hY3Jvc1ByZXNlbnQ9Im5vIiB3OmVtYmVkZGVkT2JqUHJlc2VudD0ibm8iIHc6b2N4UHJlc2Vu 55 | dD0ibm8iIHhtbDpzcGFjZT0icHJlc2VydmUiPjx3Omlnbm9yZUVsZW1lbnRzIHc6dmFsPSJodHRw 56 | Oi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL29mZmljZS93b3JkLzIwMDMvd29yZG1sL3NwMiIvPjxv 57 | OkRvY3VtZW50UHJvcGVydGllcz48bzpUaXRsZT4gICAgPC9vOlRpdGxlPjxvOkF1dGhvcj5nZmRj 58 | ZHNjZHNjY2M8L286QXV0aG9yPjxvOkxhc3RBdXRob3I+0L/QsNCy0YPQstCw0YvQstCwPC9vOkxh 59 | c3RBdXRob3I+PG86UmV2aXNpb24+MzwvbzpSZXZpc2lvbj48bzpUb3RhbFRpbWU+NTwvbzpUb3Rh 60 | bFRpbWU+PG86Q3JlYXRlZD4yMDE2LTAzLTE3VDA4OjAzOjAwWjwvbzpDcmVhdGVkPjxvOkxhc3RT 61 | YXZlZD4yMDE2LTAzLTE3VDA4OjIwOjAwWjwvbzpMYXN0U2F2ZWQ+PG86UGFnZXM+MTwvbzpQYWdl 62 | cz48bzpXb3Jkcz4xPC9vOldvcmRzPjxvOkNoYXJhY3RlcnM+MTI8L286Q2hhcmFjdGVycz48bzpD 63 | b21wYW55PnNkZmRzZjwvbzpDb21wYW55PjxvOkxpbmVzPjE8L286TGluZXM+PG86UGFyYWdyYXBo 64 | cz4xPC9vOlBhcmFncmFwaHM+PG86Q2hhcmFjdGVyc1dpdGhTcGFjZXM+MTI8L286Q2hhcmFjdGVy 65 | c1dpdGhTcGFjZXM+PG86VmVyc2lvbj4xMS4wMDAwPC9vOlZlcnNpb24+PC9vOkRvY3VtZW50UHJv 66 | cGVydGllcz48dzpmb250cz48dzpkZWZhdWx0Rm9udHMgdzphc2NpaT0iVGltZXMgTmV3IFJvbWFu 67 | IiB3OmZhcmVhc3Q9IlRpbWVzIE5ldyBSb21hbiIgdzpoLWFuc2k9IlRpbWVzIE5ldyBSb21hbiIg 68 | dzpjcz0iVGltZXMgTmV3IFJvbWFuIi8+PC93OmZvbnRzPjx3OnN0eWxlcz48dzp2ZXJzaW9uT2ZC 69 | dWlsdEluU3R5bGVuYW1lcyB3OnZhbD0iNCIvPjx3OmxhdGVudFN0eWxlcyB3OmRlZkxvY2tlZFN0 70 | YXRlPSJvZmYiIHc6bGF0ZW50U3R5bGVDb3VudD0iMTU2Ii8+PHc6c3R5bGUgdzp0eXBlPSJwYXJh 71 | Z3JhcGgiIHc6ZGVmYXVsdD0ib24iIHc6c3R5bGVJZD0iYSI+PHc6bmFtZSB3OnZhbD0iTm9ybWFs 72 | Ii8+PHd4OnVpTmFtZSB3eDp2YWw9ItCe0LHRi9GH0L3Ri9C5Ii8+PHc6cnNpZCB3OnZhbD0iMDBB 73 | MDQ3MUQiLz48dzpyUHI+PHd4OmZvbnQgd3g6dmFsPSJUaW1lcyBOZXcgUm9tYW4iLz48dzpzeiB3 74 | OnZhbD0iMjQiLz48dzpzei1jcyB3OnZhbD0iMjQiLz48dzpsYW5nIHc6dmFsPSJSVSIgdzpmYXJl 75 | YXN0PSJSVSIgdzpiaWRpPSJBUi1TQSIvPjwvdzpyUHI+PC93OnN0eWxlPjx3OnN0eWxlIHc6dHlw 76 | ZT0iY2hhcmFjdGVyIiB3OmRlZmF1bHQ9Im9uIiB3OnN0eWxlSWQ9ImEwIj48dzpuYW1lIHc6dmFs 77 | PSJEZWZhdWx0IFBhcmFncmFwaCBGb250Ii8+PHd4OnVpTmFtZSB3eDp2YWw9ItCe0YHQvdC+0LLQ 78 | vdC+0Lkg0YjRgNC40YTRgiDQsNCx0LfQsNGG0LAiLz48dzpzZW1pSGlkZGVuLz48L3c6c3R5bGU+ 79 | PHc6c3R5bGUgdzp0eXBlPSJ0YWJsZSIgdzpkZWZhdWx0PSJvbiIgdzpzdHlsZUlkPSJhMSI+PHc6 80 | bmFtZSB3OnZhbD0iTm9ybWFsIFRhYmxlIi8+PHd4OnVpTmFtZSB3eDp2YWw9ItCe0LHRi9GH0L3Q 81 | sNGPINGC0LDQsdC70LjRhtCwIi8+PHc6c2VtaUhpZGRlbi8+PHc6clByPjx3eDpmb250IHd4OnZh 82 | bD0iVGltZXMgTmV3IFJvbWFuIi8+PC93OnJQcj48dzp0YmxQcj48dzp0YmxJbmQgdzp3PSIwIiB3 83 | OnR5cGU9ImR4YSIvPjx3OnRibENlbGxNYXI+PHc6dG9wIHc6dz0iMCIgdzp0eXBlPSJkeGEiLz48 84 | dzpsZWZ0IHc6dz0iMTA4IiB3OnR5cGU9ImR4YSIvPjx3OmJvdHRvbSB3Onc9IjAiIHc6dHlwZT0i 85 | ZHhhIi8+PHc6cmlnaHQgdzp3PSIxMDgiIHc6dHlwZT0iZHhhIi8+PC93OnRibENlbGxNYXI+PC93 86 | OnRibFByPjwvdzpzdHlsZT48dzpzdHlsZSB3OnR5cGU9Imxpc3QiIHc6ZGVmYXVsdD0ib24iIHc6 87 | c3R5bGVJZD0iYTIiPjx3Om5hbWUgdzp2YWw9Ik5vIExpc3QiLz48d3g6dWlOYW1lIHd4OnZhbD0i 88 | 0J3QtdGCINGB0L/QuNGB0LrQsCIvPjx3OnNlbWlIaWRkZW4vPjwvdzpzdHlsZT48dzpzdHlsZSB3 89 | OnR5cGU9InRhYmxlIiB3OnN0eWxlSWQ9ImhnZmRjZHNjY2NUYWJsZSI+PHc6bmFtZSB3OnZhbD0i 90 | aGdmZGNkc2NjYyBUYWJsZSIvPjx3OnNlbWlIaWRkZW4vPjx3OnJzaWQgdzp2YWw9IjAwQTA0NzFE 91 | Ii8+PHc6clByPjx3eDpmb250IHd4OnZhbD0iVGltZXMgTmV3IFJvbWFuIi8+PC93OnJQcj48dzp0 92 | YmxQcj48dzp0YmxJbmQgdzp3PSIwIiB3OnR5cGU9ImR4YSIvPjx3OnRibENlbGxNYXI+PHc6dG9w 93 | IHc6dz0iMCIgdzp0eXBlPSJkeGEiLz48dzpsZWZ0IHc6dz0iMTA4IiB3OnR5cGU9ImR4YSIvPjx3 94 | OmJvdHRvbSB3Onc9IjAiIHc6dHlwZT0iZHhhIi8+PHc6cmlnaHQgdzp3PSIxMDgiIHc6dHlwZT0i 95 | ZHhhIi8+PC93OnRibENlbGxNYXI+PC93OnRibFByPjwvdzpzdHlsZT48L3c6c3R5bGVzPjx3OmRv 96 | Y1ByPjx3OnZpZXcgdzp2YWw9InByaW50Ii8+PHc6em9vbSB3OnBlcmNlbnQ9IjEwMCIvPjx3OnBy 97 | b29mU3RhdGUgdzpzcGVsbGluZz0iY2xlYW4iIHc6Z3JhbW1hcj0iY2xlYW4iLz48dzpmb3Jtc0Rl 98 | c2lnbi8+PHc6YXR0YWNoZWRUZW1wbGF0ZSB3OnZhbD0iIi8+PHc6ZGVmYXVsdFRhYlN0b3Agdzp2 99 | YWw9IjcwOCIvPjx3OnB1bmN0dWF0aW9uS2VybmluZy8+PHc6Y2hhcmFjdGVyU3BhY2luZ0NvbnRy 100 | b2wgdzp2YWw9IkRvbnRDb21wcmVzcyIvPjx3Om9wdGltaXplRm9yQnJvd3Nlci8+PHc6cmVseU9u 101 | Vk1MLz48dzphbGxvd1BORy8+PHc6dmFsaWRhdGVBZ2FpbnN0U2NoZW1hLz48dzpzYXZlSW52YWxp 102 | ZFhNTCB3OnZhbD0ib2ZmIi8+PHc6aWdub3JlTWl4ZWRDb250ZW50IHc6dmFsPSJvZmYiLz48dzph 103 | bHdheXNTaG93UGxhY2Vob2xkZXJUZXh0IHc6dmFsPSJvZmYiLz48dzpjb21wYXQ+PHc6YnJlYWtX 104 | cmFwcGVkVGFibGVzLz48dzpzbmFwVG9HcmlkSW5DZWxsLz48dzp3cmFwVGV4dFdpdGhQdW5jdC8+ 105 | PHc6dXNlQXNpYW5CcmVha1J1bGVzLz48dzpkb250R3Jvd0F1dG9maXQvPjwvdzpjb21wYXQ+PHdz 106 | cDpyc2lkcz48d3NwOnJzaWRSb290IHdzcDp2YWw9IjAwMEQ0N0MwIi8+PHdzcDpyc2lkIHdzcDp2 107 | YWw9IjAwMEQ0N0MwIi8+PHdzcDpyc2lkIHdzcDp2YWw9IjAwMUM1N0E0Ii8+PHdzcDpyc2lkIHdz 108 | cDp2YWw9IjAwNTc2ODUxIi8+PHdzcDpyc2lkIHdzcDp2YWw9IjAwNjk0RDg5Ii8+PHdzcDpyc2lk 109 | IHdzcDp2YWw9IjAwNzU3Qjg2Ii8+PHdzcDpyc2lkIHdzcDp2YWw9IjAwNzgwMTk5Ii8+PHdzcDpy 110 | c2lkIHdzcDp2YWw9IjAwODQ2QTMxIi8+PHdzcDpyc2lkIHdzcDp2YWw9IjAwQTA0NzFEIi8+PHdz 111 | cDpyc2lkIHdzcDp2YWw9IjAwQ0Y3RTFEIi8+PC93c3A6cnNpZHM+PC93OmRvY1ByPjx3OmJvZHk+ 112 | PHd4OnNlY3Q+PHc6cCB3c3A6cnNpZFI9IjAwMEQ0N0MwIiB3c3A6cnNpZFJEZWZhdWx0PSIwMDBE 113 | NDdDMCI+PHc6cj48dzp0PiAgICA8L3c6dD48L3c6cj48L3c6cD48dzpwIHdzcDpyc2lkUj0iMDAw 114 | RDQ3QzAiIHdzcDpyc2lkUkRlZmF1bHQ9IjAwMEQ0N0MwIi8+PHc6cCB3c3A6cnNpZFI9IjAwMEQ0 115 | N0MwIiB3c3A6cnNpZFJEZWZhdWx0PSIwMDBENDdDMCIvPjx3OnAgd3NwOnJzaWRSPSIwMDBENDdD 116 | MCIgd3NwOnJzaWRSRGVmYXVsdD0iMDAwRDQ3QzAiLz48dzpwIHdzcDpyc2lkUj0iMDAwRDQ3QzAi 117 | IHdzcDpyc2lkUkRlZmF1bHQ9IjAwMEQ0N0MwIi8+PHc6cCB3c3A6cnNpZFI9IjAwMEQ0N0MwIiB3 118 | c3A6cnNpZFJEZWZhdWx0PSIwMDBENDdDMCI+PHc6cj48dzp0PiAgIDwvdzp0PjwvdzpyPjwvdzpw 119 | Pjx3OnNlY3RQciB3c3A6cnNpZFI9IjAwMEQ0N0MwIiB3c3A6cnNpZFNlY3Q9IjAwQTA0NzFEIj48 120 | dzpwZ1N6IHc6dz0iMTE5MDYiIHc6aD0iMTY4MzgiLz48dzpwZ01hciB3OnRvcD0iMTEzNCIgdzpy 121 | aWdodD0iODUwIiB3OmJvdHRvbT0iMTEzNCIgdzpsZWZ0PSIxNzAxIiB3OmhlYWRlcj0iNzA4IiB3 122 | OmZvb3Rlcj0iNzA4IiB3Omd1dHRlcj0iMCIvPjx3OmNvbHMgdzpzcGFjZT0iNzA4Ii8+PHc6ZG9j 123 | R3JpZCB3OmxpbmUtcGl0Y2g9IjM2MCIvPjwvdzpzZWN0UHI+PC93eDpzZWN0Pjwvdzpib2R5Pjwv 124 | dzp3b3JkRG9jdW1lbnQ+ 125 | ------=_Part_154340_179928730.1460022912314 126 | Content-Type: application/zip; name="clean_wordfile.zip" 127 | Content-Transfer-Encoding: base64 128 | Content-Disposition: attachment; filename="clean_wordfile.zip" 129 | Content-Description: "clean_wordfile.zip" 130 | 131 | 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 192 | 193 | ------=_Part_154340_179928730.1460022912314-- -------------------------------------------------------------------------------- /test_mails/05_mail_with_both_infected_and_not_word_in_zip.eml: -------------------------------------------------------------------------------- 1 | Return-Path: 2 | Delivered-To: 3 | Received: from myhost.server.com 4 | by myhost.server.com (Dovecot) with LMTP id xgh83hdlskfhwAAnDPNtw 5 | for ; Fri, 06 May 2016 12:46:30 +0200 6 | Received: from cable-178-148-132-143.dynamic.sbb.rs (cable-178-148-132-143.dynamic.sbb.rs [178.148.132.143]) 7 | by myhost.server.com (Postfix) with ESMTP id 837HD804C2 8 | for ; Fri, 6 May 2016 12:46:29 +0200 (CEST) 9 | Date: Fri, 06 May 2016 12:46:29 +0200 10 | To: "root@myhost.server.com" 11 | From: Craig Dawson 12 | Subject: Re: 13 | Message-ID: <11162134532523c082c8e459be9dccc04d@sbb.rs> 14 | X-Priority: 3 15 | X-Mailer: PHPMailer [version 1.73] 16 | MIME-Version: 1.0 17 | Content-Type: multipart/mixed; 18 | boundary="b1_111612a42c23c082c8e459be9dccc04d" 19 | X-Virus-Scanned: clamav-milter 0.98.7 at myhost.server.com 20 | X-Virus-Status: Clean 21 | X-Spam-Status: No, score=0.1 required=7.7 tests=BAYES_00,HTML_MESSAGE, 22 | UNPARSEABLE_RELAY,XPRIO autolearn=no version=3.3.2 23 | X-Spam-Checker-Version: SpamAssassin 3.3.2 (2011-06-06) on myhost.server.com 24 | 25 | 26 | --b1_111612a42c23c082c8e459be9dccc04d 27 | Content-Type: multipart/alternative; 28 | boundary="b2_111612a42c23c082c8e459be9dccc04d" 29 | 30 | --b2_111612a42c23c082c8e459be9dccc04d 31 | Content-Type: text/plain; charset = "iso-8859-1" 32 | Content-Transfer-Encoding: 8bit 33 | 34 | Good evening root, 35 | 36 | As promised, I have attached the spreadsheet contains last 50 transactionand your account actual balance. 37 | 38 | Regards, 39 | 40 | Craig Dawson 41 | 42 | --b2_111612a42c23c082c8e459be9dccc04d 43 | Content-Type: text/html; charset = "iso-8859-1" 44 | Content-Transfer-Encoding: 8bit 45 | 46 | 47 | 48 | 49 | 50 |

Good evening root,

51 |


As promised, I have attached the spreadsheet contains last 50 transaction 52 | and your account actual balance.

53 |

Regards,

Craig Dawson

54 | 55 | 56 | 57 | 58 | 59 | --b2_111612a42c23c082c8e459be9dccc04d-- 60 | --b1_111612a42c23c082c8e459be9dccc04d 61 | Content-Type: application/zip; name="zipwithinfectedandnotinfectedword.zip" 62 | Content-Transfer-Encoding: base64 63 | Content-Disposition: attachment; filename="zipwithinfectedandnotinfectedword.zip" 64 | 65 | UEsDBBQDAAAIAPg0yUgSqfEZCA0AALUPAAATAAAAY2xlYW5fd29yZGZpbGUuZG9jeI1XB1BTWRSl 66 | 19CL1KUqTXpTNoA0CR0pC0iXThK6QqiCICxBepEOAgGpSglKl9CbSJESCSKioCwgCKGzwZ2dBd11 67 | 9v15f+bPnzP/n/POPfc9Qx1CIkY8MjIyvF3+fhDemUGJm7Y+ThBfcbHTe1Wyng6hCuD++hGT2toc 68 | RCSmb2SHV9WTd5s0MnN1bXSEVNqt6gSjQtFkGFvQ2wLb2uAYg44cF+jnwOEqqHI3FW+JV00S7gFo 69 | RXEuu3HxJjOlxAntTCvhckzkI3t6rYSYbt3VxzKS3mXZPM4GRr3AW7x1pUnec0jswzCpgnzJydfu 70 | fZbNBA0HoEN5uUDmWv7IS842CKuauitiTPowx1d2RbDH3nvuMmmAAGvXo6DCK4CRDWMZzZcq1bAv 71 | FlfMkY1pgiWW1/1P3GQ8gwdbeiGvbN7Bhx+hZti/5hvqkJItj+LzzeD4KhLg4f23InS46e/p4yju 72 | 6OlwG+rk4ScWAIWUmOglZEvQtmLNDOcZCppvlrj22zz6LWhZ2MhoGzKE99vRRXSkXlhI74uOUJDp 73 | hGKHHWYoW94hYZ0rrwSkb8c5Vc6/p8BHLogAV88mHwJDLaGgd0BTWaabKlojb/tQO+NHhhNZ2/Sp 74 | Bokqz1UzihvLvSoJtea0fZwbxh/YSopSzsnkqMjwX59O9sNk8+o5jN5kN4yRndX+5O2G9gPMdFut 75 | ZFxu1hEl+3Sj5vMzbwb0iu2egZFuKErWghQKdpAl/PiEzigOdnHJHdFfJ0JBDfSNfH7JF7qywbsE 76 | 5mmms2KZ+3XAoofNqWvN2jxR/CVmg1PB3c4erNyzM/cJowtJtjauKt7x91TtBay5dhNjP+oswMsO 77 | dxzKMTurJ7ude0mVAZA0ZD39FTJQKtFtR3id4co+R+Xzlp2dW9THl7lZYxmYLsIoTCmTSbpIov2e 78 | Sna0oe53enJZZJhYXbybUBRCzGy1SXmxClG4y3F4xCK7GQUiHdKMr8t0EgzYCZrtFOyqj1aOeQt6 79 | vzc1+rVweePAw7cukyPUEB5GLKN0Ze4NCdcgeq27lvAmIbUvM7NNNrFb/Y60kdt6g1UHEksX9zgv 80 | EMZRIke/ArYL9rC4IhNUcWSiqRYF1wi47NYrmg4364ykDKokyBF7ACqFamc0gw8unHoFPAsO+AMf 81 | ZwXin3mF5m+v+PrBIE6+p07pNl3XR0vQnrSVxhy2FnPLmDLhP63VBpVl9yDX433dKvfng6kLgYN5 82 | DuId8GE58hK9aQoQOqPH3yU/DHuQWyPg0mjEL2+XBE5LQt2T1Lomae3VMNTXWLP1KpJ/uUy4bJRC 83 | qpaE36V0XT9LqKkBT/cQFjV+iS/N1zkL0SecyEhTj7/kUIJMVvCcdZp1tWSpYOXe/2pUrLn45cDE 84 | qBqU8WHjWQbIx1n20xinhKixdXqOiu5SAlf8onasWLjjAV7SRLW79OYe4DbwY0TzbSI1+n5Ehk6J 85 | 6HBUzetFntK216p1lIJuohwk46HIqpmbX6SnBuzHdIWkRqR9un2fKLX+1i9rbkPPVT7CpMRAELvG 86 | cXW4L2qyBVT9uNNRP6pPyDKKCiAt1YjHc23Miw8Dw1IxsV5VWANKv9kmL0/Prbn7qrv3D/Ra+SsI 87 | ndVVcWB9bILYhQW7Web0tXTwnapnH/uxklk7MXDFxXUf8OKM7MRYSQmCYqp31CkpSYNWW32WKW1U 88 | xXPuaTHXlqDzPllBplzL6PCQiF3DcnyqCXd9Qz36OPs3XUror3b1d/bntVlm5szCaUI5ngxMfQoU 89 | fnMyJP8iRrSUrFtAlj8lwnJqr31/e4DtyUvhZwQSlO2fw4nVWBM0Pr61FSMS68HnF+U0Vy6L7xFn 90 | lnOVrASzw9AMOYBleRgcwD5wxzQm6cCWXeGEW3Xw5X3DF0zBj1F+cOh0IME0OawTy4zuVYO0I6tF 91 | 4qkDZRUDostn3HKrfh+/EGpWNKUcgRwGN6yGz1GsVVyDxm2HhZjl/aEhivo1EDGp5tteMHlg3c8V 92 | 6vIMGwcT1Zl/gcF3SXs3zbfiturJQTV0z74Nwxvc2Dk+f2pfrdI3/BG4mMOS/cy+HH/b91sHOBd4 93 | f3WDJD2KTgnaKGwe7xAGYUqbhPBQ9xLZxpPMRb6QI+8XDAwijL0p+K5z6TBkXVLc1vYg3nqUFPrg 94 | 7mWt8MIbyDEEnZdg4/jFSrhWW6++kRV51Z3wbnYGonp1j26YoeiOFP/rwt9TZvdtjC6HoHJNBGa4 95 | 80tWIg4MSBgJL6FNCQ6rB+6myUe3P4Fag6PYK0YU2diS3oZ7cN4Hpbap0G+UpfThK4kpH5QGQ6nS 96 | NqcX0C8qtoe7DmuUrz16uPTxQl5uaZTWCOkCs/+pHHviEh3dOLIM/y/5fZ38/Nw8XL7Vs0a8DnWn 97 | BEAdm8O7+Uty7VvjpNpFgg58VXCB0S3Np4UdK+Qi6UXy0oHNRyIaW128fCnRzgsd3NvAOL8tLyXO 98 | D+wSj8Lv02aOotiJd+Yg8g0R5Q0XEUqJ9SnRze4eVWnH8UJ9EdJGTmZiQq4PlpwFxNTE3hSBchGu 99 | GSNVEAo+FTNx5aF+XWjPGJ/bpBvHNBfLBtsvNwwWfExGGjYBr1Cz0LDpY+sm8VOOTvuveUtwDEbx 100 | fsaR/m+Ozp4efib2tyBOpySrDFX0URK0LVsUg/6f6kkjHyGY6N9FBhYs4Vu6jJvXTiVN8X/FPtMX 101 | 4ekrLMCs5s5kzlhUK+cyKZLI5QlfzWcy4nbDOd5kMwTrcyJIrFnAVilQ+Z7OotrMf7AQAtRgdst9 102 | xiE80zOdpv3U4xX+y+Do1Cc9pKSSISgVKqUJ6w22zJSmOvcbjDQD4TywdPKpvl2EJFt+67EXyJuR 103 | 3JUHQflMlnNw8mouizZBhup6nBozO0dXtv+1zAJLsjgBfRpksgaPKYbQZfWLFFVmXviI+pzNB0Ui 104 | wiC4AOiBc7tuo2Yj9s4Y2BtcU6qeicwCwklRhwYVZnW9iYPMlgBEf7Xd4qXCC7n5IRWPHbcsMzYU 105 | WWkNiNmDmpRG8mMVlAO2iU/FfdE7Q8aJawemhD8TlxY3cUVk6OPp5Stu7+V1Km1uQqs7SoLxOlYp 106 | is4CKnQ/ktYx6v6o0CjAY6pAewoZu33PjzOnsWZW9dPzxYWOhYNde6SD972Hcf5UFmZxjG9Tp3q2 107 | qWBg2wFOIXrt0qVlTpn31L9zdDG0q3NxPwo3eR+hQ8VKi8obKyZ5ImVq0QxR4Krqinta2vY5LiLF 108 | LIU4ut4ZomN/3YvNj/UXlU3LN/Mb7Tf+mMS2fcVg1NbIMYGpDuDLZD0rXBsfaa8X8X/YegdR4m05 109 | nlhG3JpElJu5LCL2FNaR3Z02WxMGhiDWOsjtnKGywaxxJ7itY2pIqj3o1vBKhBQwPvabA6H5meIf 110 | cRKE4v+0ys6K5ODp882Aacm2Bp0SjN3zR4w6840edjEyaBAjLLlLPa6nEdEa19MAnu18i0YzlZW1 111 | PFy1x+aNzyseGY/s1OrdsatcNomKvaxJLvjauzCLorWaeYVTu7IodhxpbFhIaC5T4amNIP8MAGbr 112 | 3/MmgLxTGaBHiNR76zPEiaLvFsttUS9133bhv90mQ/U6NWSMZEPwpYB5RFGOQiypXTkfXW5ijkYx 113 | JksO7wpqNIJo6bJYLUFYECeLW40Y2UCUMYX/qNdkIHNRkKeGzE5I/vai3fYe5WFbcCC5pV4UW3ws 114 | fvmgMW0nwJAU+AXPh8P/N0wPzdYkwWRRy8Pi5yeIimDVHuNkA2XtQ/ARHvDYH4G5a5gIyJtme59e 115 | 7vzZoOFD4gjXPQ5LVEjDRFNJ+0wDXmhYTGq7NkDcoPWkuL1VN4wkco/kzq0A59t093ukTJS5jI++ 116 | WRVF1JeiiVuBhz/NOgbctFTDZQAu721NYF5Ovtan69CaqmyAC4JubGhsn4g2Bwutqtav3JKq0RJ8 117 | Drb08N2EYET9fJ6vJS9RGV39E/Pa4PUjzJxWNTNY2lGErCy3agZ2oxZQ6aeNrnamGYaGdjNvWUxC 118 | gHbXG5jBr3uXa65qztyjM79Ck25z80JcjB8VJ1ZVCU69q2Fh47MCIWLfegicbmkSsBqlqC0h+6LP 119 | UsYaBpDgFh/rr83O5ZYEX2rhqmdM2vySZprvupeXmopmW23olRa2Owwaq6CMFRGwBfDEmuzy5YEt 120 | Re0nBOS5cojUsx6yxn7pJoMK+Nq9AdwtNXi6ldu1jjj2ZjlB4keyCH22/drmnxc3Sl9PZ0CYo4Ry 121 | DREk6s94AXtP9KsCQaXW42tLqSi1mP2NfU2k38KFo/Z14ON2ehem+eMLW/LfUoGXTUsYJ7UN0anU 122 | +ASMeP+IffawQYl3bpw/cn0PPLvzpDsHosf/l5PJ9/CznZ/mHFyJ8LvN6o/gs32S4xz4iPhnW4Uf 123 | WZztRudZAEj/pct+Dz+bt/Tn4BukPzawH/Fno4j2HF6P/PuM/hF9toTO/7wPxQ/h9SP8rC0YzsF3 124 | Kf+16gx1iElOX5PjLiDumw5Up09/AlBLAwQUAwAACADOuHVIPcynQLojAABGOAAAGgAAAHJlY2hu 125 | dW5nLXBvc3RtYXN0ZXI3ODkuZG9j3FXZjtMwFH1H4h+qvqKSpBulgiK3tLR0hZZub67jpGGSOBM7 126 | STtPbAIkHvgC+AZAQmwCfqH9I27SmnUYxPKENZPa957je3zvdXLh0sqxUyH1ucXci2ntrJpOUZcw 127 | 3XLNi+kbo0amlE5xgV0d28ylF9NrytOXKqdPXbjkcJbBnmdbBAsgpzyfmZZ+MT1hvn72MiOBQ12x 128 | w0blCIzSloKQLi9HF9NLIbyyonCypA7mZx2L+IwzQ5wlzFGYYViEKjFTyapqLpk5dnpPDy+mA98t 129 | 77mZz9wMcMvhF1ykqSchd1ESfZLB7ZOV7a0da+Fjf73TRphPJR87v9oAEDFN+4YWrX43IThYNS1X 130 | yA3Yr8+5/5EMXQAlsPRyLdvIa6qmZoqFai6jabqWQdnzjYyqIqSqNS1fKmU/6+Ten1VO4R5sEpUd 131 | HIMHPuXQDLuOAit1FlTXqd5f3PzsclnsYWT1jQVklLmHCXSjF9v9kKYr0GOW6UI66zZ1AMqBGGL7 132 | L5QqlQusLHt24DOP+sKiPLaOLGHTSgrGBUWuwIwCsWR+xTR0ovP4jxDwSzMAOpiL/WrzcfNs82J7 133 | d/Ni82z7KH4CdA+Q8Os0tOJ7WcnFPrlKBDCB7ZHl0EoBXF8twVfzKRZUr2RVrZhRoZrnRmqprObK 134 | qjoHsPRLPUMc/gDOqhIsEQl8gE04vwb2/RRs8W1PbHIaK1hiHxNBfXBkwfPFsHMzx8PuusJ1Q+cG 135 | +KUlkWS5MkYyTeIC2/Sxt5TB5fq7aBNLLIdxY8i4x7qAMqZ+kkkN3ncwYqg0wfzYokdlg7kimejU 136 | wIEtGmzXZZgTy7qYjtPPUz0apa4zB7tx4xoYUs3Fsb5lBrv8eBrhP5oVEPaVBC7W9k7V/tXdN6qB 137 | ZYuWO4w9Lga2vAB5IAPQxgKOFLvBlRyiw8gB1YcCC7hJcBMg9teoGgviG6cVirCBjAkQsfYA78ka 138 | AEtmBHaBA+yRLfgU4ORexmqkmB7zHWwnG67KgdVLXKudb/N083z7aPtg8x4uxJtdUDicLrmqitT8 139 | Oe3y3jPw402SnMgtjksbyDmSO2Tz0pIh/HujjV1T2q7f+Kp+crmwdCgYup4ZoqQeew3K/sA/5ojI 140 | 9jsxR+oPSbq8g6Y+93kq7rWfJW17e/N+82HzInm+SW0fbm9tXm/vbe+k4A3zfPMK3i/3N8/2B6eO 141 | 1bR0nbrKScIFXtj0RNHaTyqbGsXUX9UXJD1Obe8k+t6B2GP0/VaNZSlA/8L+PGm5cetE0DjpzyfT 142 | VxgIO3+N2nYX79DM+znUpobYeTW1dIx/wYRgzs/5vmUuT9hA+VqMXJ7cWLbFxYnlyR5TnlQHWD+p 143 | zJPNS+gX6KSPm9fwfPuH/fJFwXL/ESQkaYgf5Hzxy475Ntw/uvz/e2PI2e67xMgw8LzLWOAkuuXG 144 | 01SSeNi105qOwgKtXXFVVFTPdXvVy1bYbZ91OEtXrk16Kp7O7dHEFvNrCCED1REMJR4RQlWlmr0S 145 | /6J4AEAOM35UJ/Cfd/saqWWr5PSp8fzgDDnq9Q4KtYPivDcc+nZvsvRaV/3sgI6vWt3cwB2Jlnoz 146 | 64yumlqvWRh03ckI09lwPpz3sz1PPXMNDW+wwxo6fWptssN2LWoXB369N5muljOzXTxs+2bz+nje 147 | I+PpdM0DZ6iQM0V9frXtrI3h0uwLLyysjo6Ocq7SN0qFc4piKIWj06fOeVpoZMMztRxRwn7kzute 148 | vlY6uOnn1fFBuOaDq91qg7NWfmb1tdLiyupmZz7LlQaLoh+FpSoci1sROuoiasFJGxGaIjZG9Smq 149 | 3kDrG6g9RYOGWl83VyZaoGuTK0etaNmpD6M6DdByVrWjbgeRAI1nyI5avnnt0Gw2zIaPZsHpU2h9 150 | WO2Ry/Z1NwhDki+dsXnfUQ+IorVn7cK0Xq8ZrfDQOZx1cVSttkcYC2b7V7UrxbwSjPzzzXGXqoGr 151 | UTvfPH2qdP1y7sr4Cmmiw9Z6PlwOsmQSeAf5M1rx2vJy0yHF5sBZdArOJDjvKQN1Uqtfr3+iuLx1 152 | ZQWCIJpfab+EAO9CvF+8WTLcwuK9+/rHk4gQYqanqk/1/FgVZC3r12Pjm+eD4+vFd+wDr79viIOf 153 | N9ffGg9TVN3gODix2sDH/QwQkRmD39OTfRVmZBSYCqI0O657vgnHe2KsnfZ5xJsZa6b8G340zTMm 154 | cNZ2OtmkZNtS5xi+7UpPKdnwOdGUYbySFVGmXRgC46ayhfWPYy/hzk/chV8iJJf1yfzmkgEm9vXH 155 | dgwPMdXCqhMTT4yYMBF1cHFp0Iy0Mu+UU4PRrid29pko48ODL0ppLQ2yVFFG/zEMzXB0ZQw/xRns 156 | R4V9MoxP5pBHijCsUHKLy5g88wj9tgEf4rSQsRFmMZjMYL7xjEoIQotfkf0wNvaIMoI8CF+pq97p 157 | pb7+ODE/UEvkjJkZ9kuyZFNUKM2LeUH8ePulAhKxvu+gF3DENTvcseZioBtpSzgsMlxoukTavjhY 158 | QUkPff1hCYqPajVHMhMEpCt0rK4GOrPIq1wcLeKPLKQPS+YMe1PZFtzQNuHmRzTONVVyitJYkhuw 159 | 9jIa6hK8/vzRk9t4CDlFD74zx7l8wFXPEwkdYVLm+ZGPwU0nD8CVUXendCwwyktG1YlGxQk375oX 160 | 2Qda7K0y2uvvRN5NcJJVPQ0r5duQoHa8I45qKXXsw5WvVsgedeze+Nu0DQ8p1o9w9VKs9PajRjPm 161 | EDKrzASpD4qsrz9VdSVzq/Kv5u3z7rbricdhRDNKS3yha/2I9nYW0bn9rp+SO4R+EnsC9eiGFgQU 162 | N6jVJOTuogBQXzb3VJpjUd2VDZKgwkCL2C15ZnqQqLrpqsl0LaCngC0o9VHgYY8A/FCpDinhW0Kg 163 | p97B09RXKZKLXVR5+OtPGsSd9Fqq6aiE/2YNM7Q0kQejP5PNJbN3h4B+H116F7vdnSH2+02t532q 164 | yvqttiLkTmmD3WSYwmX0X39H0dxcSOgC+B4F1yUx0ZtmkWQxUixEy6aR61C9cKFakKsZiC7oVU0s 165 | BmyxX33Km1yY+1dxdVE1XUV5euHaBPc+Za1J9IeiWwfxm940kVxBLTkF5uE+KUEvfRicmwQAVygE 166 | ETJgC1JosIkj3Oyf5wV+enK93NcfLJQ/xEE/Ml1/vfzgGRpntJV9d5tGl36FbvcnkYt6o7Rfg2M1 167 | 3Trjaoq1XdAbsY2hfaigP3E+upPc8vqrxTM9AISYvh3nEf3WU+C9q5Ul1njXLFuxYbUxXYvGA1gH 168 | gDPpTxK9Zy208MVn5rLZk61JMW9FBmHk9YcSqUNiqYKugqU2BYiS6ygaILfsXQtNwX15qE4QHCpt 169 | WIV1FjmjeAeT/nw1iLwV81V99qSjZv0OoqezmoeshGWeW4oAQ2KcnUBIu6za0/bVg0GDycQGorxi 170 | r2rfQ4fj4q7UmprWZOqYp3hPOxn4ENT43VAeezT1bk4/17wQ6PjM9GRC6WhJuasC99XeRYuDolS7 171 | DhfeTwKzrfuNQWurWFd/ptp8XX6KWA6aAAuR7w97Cczvvf4Mf7bruqku0eZPd1eEXhiFyhIA0Ala 172 | Mnd9OEEF+AQg7WstHOUkP0Y5ON5NISvnECRme3Xo+WTWOUgQaEBUGXk7o92iftUQe31znKcK0YQI 173 | Wgw0cA+WEVfhDkU+4sRxE1VS3q8h0gWqaON9eZkd4WP79Knp9bO5D3JsViaPvyHs/JzblXX2kzxa 174 | pQ6LOwBxq6vroTREz2yars0Sh8S9d93hiMS3CtIZBqTwEb3+doAo3F9Qp+81KGqgdxFhq0W3o6/0 175 | Iw9CViqByzDA0lff8D6QmRASoCYCYTSvJ6o8q3QUSC24vZSpx73TZdQR5GnndLH3b/N+wajrE7Kv 176 | 2aagNUCsY1/ODj507XzCflwcd92rkL0X+OnNqKX20LjHDTnhMWi9/rxc63iCoj/O2Dt6KkiBdUGC 177 | JfT+0B41wmuJfsoxkesfYRDmgDIdsto6x9U5atT6Wf0cWLPFznFoefn6Szt3x/UEM9zeT96/vGMQ 178 | mdRtxWsuHlfFtgLKEzmIcNd9QNiopyitO+lFYmaky8FeY0apCarTF6rh2RuYN14YXaMFSz2bRoWr 179 | 8E1KlVZTfjsNmA+bv9Xks+xJXvwYXGTMKcBX38FIXA8ctjXGTXcomfTYcDKfBEQAKbJxrskUpH/c 180 | tJrHAs8URXGi6QEUZClcHgrwek7TJLjLUSVIk5W7bqvUVA+3e2mBKQYM4UH0/vobh6HQSMj0GZLL 181 | WjbXLzPQLiDsqQgDMBntwqLTgcD80uf3xiOAKTia3eETTMEMssCoA11J6xk6Fv4Tad6pb+RHu8Sl 182 | VZ7LICrdb1wjwZj+DF8MsdFkNUgiM823RZKs3ts/gKzzPoWoHVS/wfSsC2vjh2KlpXgc0la9arHN 183 | hwWCm5R0xQDEQ+hvVni/C17BqOb7UA3GzhI7OKhaB4a1/E8Ir2yJH5PrZ81Y0DU1p+b6zCGFtoaF 184 | JnCfqigPw1reuxNZX4LAQkm9RPFAPyc4p3xSNNgNCp3Yhsud9d7aYv3qNKM4Dclm7tLO1G31+kuU 185 | mAWUwSEEzEIugOkwDeLpgSzz8h2hDJMj7caYeChZzYlXb7PidW+KzoNJ4xsbDA+MbYeDW8pVGOJx 186 | CCdDqjETkpSGatZLHroq5FU8ha5ouRbi93a+NtaGrZWd+iLKRl+HbAcQYnDfZfuZTVNh56JNiM7L 187 | nqmGU8WE1vEhQdHnvGxfTcJE0FZ5Te7pu2Y1hM3qV/H78Rjzy+HdX4JBWFOcaolaho01jQ0+KF/K 188 | ILueCb8vbo16p/EeeK4hW7H7jpDgsJQfbfb5fHYZZnJ0pYhGmeF+4rsZDBW5Rz9OGzQqb5kD2wET 189 | FKJ70J4ELMy7zClPgzYinW0m2u1Wudtt3acGT2Bpr20AWM/WRsT6N+vnzWFwmnK/nE0rf6TDpJ3u 190 | fpJj1aSfzIrNBNfV/vxsJ4Ry81zaAUjEc/qmMPh6COVnOocbu+n+fkZ8KBjRyFMqRVIFis4vhvGd 191 | x2t6fCsukkHPzair3cxuT6OheccC8x8XGucQ+nxErZIiK/PvXEhOJWJIsib++4vVUTo8KIaJHLTm 192 | pMzPaYBQQbpkQnoY0phgEfcCi/J5pcqQ8eu3c59UqhPL6MnK7KZOuJoz+UjRu6Yttf7sRr9Eldhk 193 | 93SDA7hO8XTGVN14rz/NZ7NK/30uMgEqsybj6kxB9det2bebWRSBsGjsZRLfoutierjvLgcL9+Ho 194 | Kj7+wUJ7fOBiXcfslLznb9Xaj3Zxlgbt60mVXwvtZxK8+zHL/SZS7W/Zxi4k/tw//Gf31KTZGeoT 195 | yLnk85cI3NCvFQtfYMPpnOb1h0u98uNNW4hksPw2hOQKkVEr9QdHpNsc4sv4YSiZ5yr9Zq78fl+s 196 | Y5nl+3OqhOefK5LFll5/UId0yuS5faxSF8xm504FlmH7J1i4sQ5Q1XMh/N3EEX3bBgR8zGzs4CFr 197 | oQ0XukgiZ++S5efdQTO3vuM9N1f3owK8M5K8AweUFn0EzgdmBbrVguBlbKk8o+A0rgjEX48aYwQC 198 | c+td8NffAacB6IdJ7hjCoIpbGZDv9/z62+jtS8P4HKGWfBn5Xng/bwemc7wAgLITgEpqEroB8I5A 199 | 8Yf3VBEABC7u87qCMaXJ6w7O2SMtkK3BM4cAqZjSBPrNwu+dehkM9j16op/lBjxvU0HvfSlqGEpE 200 | q1zOBw+FzkByyncPz3GUfu55DCT/UWDeOq8qURTuj/Q/yRSYDCU5BwMGTIfJOcenv9yGkUDAaHb4 201 | 1tp0dWjRF2DGo/BfHnkHvvQZY+C/ot08UsKGOqVghyFCwfGu2PtOvmcJwppZHcwoA+M5cpR8KbFv 202 | /C5qKMG489RyGvjjxC2WCe5LuZrISvDFHiNdsxXZMLBETpEUmbOuo2MsZarmdW3whz6+VYFJn3V/ 203 | y8+9MwMWXcDC2q5z9PfPmnjRm89UER5gzPjb0J4eg8K0hYPC8XUS9BH5piTw1Spof4IDYEmIlR6Z 204 | l9gqB5TFefndvfYPYuLH3z+lJMhNnQLzjmEkIhDIDHYudplV2nntQ1OBPu16tmpQVW2DuLhSP3tI 205 | Y0SUkvyWjdBpoIIplHJ8Fe5H924bsNRAAFLw1jgxDcBAZzhTu5RUNrKbvznsaSs0PxwpzSshIpdZ 206 | 748WKLoZj+avo5fENMxW8UNj/e+fndtOFRgipsZqZFY3ODs2Ed/97oB7H4jf+8KuvrzTaYwuWSP1 207 | jb+pMOGi6YtWeM3OTp34ySijcFyQf/+CV+P4wnCDY0nWtEmy15C4HFXpK9fiOKl3PGJ7khWicFnm 208 | Nuj1BsohpLnkvSVCk0LjHORdU9PUb3t4uls9CqGWU8708rX3+QUgSKdVD5WL6YVuFmr0CAS/DpxV 209 | f997+9IIz2KDtuUOvPAZXu/NLbmEiZJJ+Zyb4UJ8OXscvFIaUZ5CQDszRfIEP78Hg4aCK5mddSvj 210 | BxZzzvRoGJytByXjLh2285G1ItjDVjeEbTEfJ36fimDOAd67/lFeeUbR7dSyWaf7FJXv8ZHKd6CS 211 | X2lBW2obm8AbTfvCHwOlrrjYB/GNQ+WUbs3bzpXHUfJ0amk9+QJEuId3DHnK4Ee6M/XX+4FCQh8u 212 | psppYlns5ZaMX8LZcbGkUhnhJLw+eikYvi7HOQerD+uXaw22Qz0tiYOz7m3jAodoAvGT30oBRFd3 213 | Kd0I8HPb6Gway4A8AHLJ7qHCs1RXWD3i85fL+rdDH5/q75/7weZ9EKNqYCObnN9F+7DkeaoaAOlO 214 | i1cdHMlGae8+c8+BmyoK1/jfmWfn94ufeQKVG72Am4wS5aksgoMmNz/T/PVSS1oOhIwZEubCxLmw 215 | MnVnxznhzYNBmTphyYsntuuNKjdmoYxGcWiBJtx6SBCTYTrKPFWfMOmhogw42Gn1qOe1kGIglh0I 216 | mBRkSgE8fwgZm2E8WQwULxf2xrIYCz0XwS5wiukLJWNLjLWLhwuMnf0g1io/r12JMN5mRoq3C5o6 217 | Yh52Eg/i5UOVGN9iFIqFDoXi+yMCzHz42VMH46t//shnpoblxfw4oxW7X+nJQLx38qktk/LO0pgI 218 | 2AgTcoanBKhAscaXe8s1AeOJNJVHIqa5Dg0bNTQ3DkRpCnTYZ35o0KNDbFK/NLOCkL0wqR88YSXE 219 | Qbe12QQv5JRDPTuxDxuKgZs+qU9D8lCZqQ+m4Ao/AXscqv6GPI3Qr/dTCx2wxDAXjg4YNz3l4z7K 220 | 2k9lvLaBq7Q5RVBxd+koJEekMM3+XgJCzIsDtV8Ofa+74yW/8XbCbIwZQD3Kwc75M52pgBTzD+hN 221 | W1kJ8AMBlI9RvUd617mQr1H2UCnYzEb+WZy/WSTFLyUJXB3rWPtI2RfuUGv5TDAqw2Bb4N2ainOf 222 | KtdH8zVZ3dG9uhINGpesntrMPsHPxd+YxEFpOiEC5amLfwh02mXIdJ03qZFnF9j/z7jk46tTJqJ/ 223 | X71BxjovOC8E+Vjk7S/+S3j1+r4OnSgBKABuHutkiaQptMJgQSZU0DtIX47psu4yTLgn327e6pE8 224 | i6KDppQ3iX+7SE8PU+YN8OMrcj2w7tkeVCQYl3sJ4kOTzVT7NU4GdJrkbqvWQXB3ZkPFo6Ipy354 225 | CcV0skclap3hpd3ard8H7HUy6EGblEs9oLhfRl19Ol0JiqgxpqUd80iBR45liEu4k5yi7+RhvRh/ 226 | 2Ss7mjNMIVT5jdiR+c3ata1awquuX025rPhKRDvYyTRdirr1OuuTllF6rWJ/qerSVgWhlLD5TPP0 227 | 1OOFCg66S+dxTKsjRxDFnz2mablUReFYHI/7D58SAA5OwnL61ydFx58Iz//E1neimNp/Udp0lA3/ 228 | /WuhDL8HFcegeIZ1NrdvL5PhySAQugtrpYJl0mEI6zQeiQ2d+7HT2JfE6GvU4IS80peftti1VD9y 229 | R52nI21VSuoJE3csDNaeVH0cGetJyRWY5iI3EkZLBZ0sUnh6SsngNXeIVwlqjUDg0i0N6DnfEFs/ 230 | wfJ5FBcPWm0A5Ldq7Yv44B49bgOsgAbpiqKCYync498kbmo+xDHkRlO6N6wxw92VkDZfz3JYpGvK 231 | CKMNH8cT06xtt4UVbLItX/2GHcIbGuBuGu+euL6o55q2/dvPCOSw1FW49ELn2LMteyFdL/cjfqS/ 232 | RtA6tE2twzMl8FFxx9yZPmubCn+XhBoptoKvFu/od5fgql1VfTODJtStSDz95MkwaVFvWEXJMRLD 233 | 2lHDQXibQYI9zIq4XMfJ3u7kNpmvLywrCandJUFzRLEaTlqsb+1uTbTPRaSXyVbKEmcOnp1qhI9E 234 | NHlEueEQDUE90Hh8Fmd2zijK1gdKm11EyU35qi+q89QGlPFXlm8iPWuofR/uRCHUTjGui1pw5Z6H 235 | 7bus+6Kx3BuwPHCO7eGCx77oxmxvM1PeUvbDgj05UAd5J7+QY0kbsHNDDpoxn6J8fEJ9ROrljBej 236 | DtA8fySAtg8nvvlUQInFE1MUWs3coyrjPekXNWIzXApbCL8vh/NwaQLpGzR1y2lbvZMvyB5AKqf5 237 | wApzG6v5PH7cD8VsJlWuXP33b8BH5BdVx5ewdZBsGpAT/wKNtjewfrvI0Sz/axHCqkVy9X4I+ak9 238 | fMs+qKl9dy3sf/A4UVVpL1+8FB734WfztDcegYEP6nF71LW90pzbLkl4YJq+swCVmK5LZA6rTEat 239 | NcqBUDxlI1uf1F1l4qQDMLjatebDhcAe4yRBPMggHdanelumsMxEEmYke4IIXKUOJfYDCS42W4fZ 240 | xWp3H/D4826c/S7CfaunYX2n4W7ggXz2Fjg7GXzMLlBax2GkOoJ7+IMR8r2n/PcWZFCnAqi9uACY 241 | GKX20c/sUA8pPyyDNs0i/rVJrSi3mWmXJwrT/iPqFduGX9NUCv25aUO7epen1anOrVczKZLuqQxO 242 | uIgsWKzLmWUvVwFxZ37GXS0fJ4N7OE0aVP9nL19kH6A7xYeZUNZ2Iq1DQvmnNpVTDlxVmGYetsN2 243 | afJI/VZ/pl+F0Syp9U77lqbdwEV/KtG/56U1/YdZLC62brUsU0ns2tBcPhnp/SiWdFiv5vB8oWkt 244 | LnQwiFXsObVpbuLuNxyi12v21q6/GJxNY93PhvDpb8dmXVMYh+EnwcWvufK52FRtJn/gYZm4EHt6 245 | 4juXV/WV159K/8iRlhl2O4jVS4t4myR0gfS11gbcMD6aXDTEmqZEWHZwOMxjYdhCK5NUW2jAXhZx 246 | f8vIqpndlOU/mezVgLp/UbAgoSggMYCnsishaCY4YxCsh6cOm7pDXcFl7cJsh2QvR0xjk5xWAazK 247 | B6zYjx8FLpjbPNbglBW/qO7UGtss3cuSnLcgXi8IQMZdOtujHMBueYDXawTKnlWuM0h7Vr6uhy+A 248 | qiZHnZm3qdVWkmt2BfEMfhLwViYc0rfvQpIASfrp6BJ1u8n+MMv6r3sr23nVBsIPhCpjdi5aiQTI 249 | RsKaALljTUhYzRLg6evwn7R/VZ2q6mVvIntmPJ6M7S+y841X1MQVMGubLalZ4NchRmtZ2Tdgaxla 250 | KAF6DJRVGnQrw90VtUGyu1Na1JkH+4IL2QkWvEoyhEemOn4bJOI77wyoLYNNpPOpwT78TtiSYlDa 251 | JGtfuXRApFySpRnObjKCdG9U7NiS41GQ07Q+nQwq2qspR0/BFb9KBa6DEKD1Yi3EXlb4vjICtJsr 252 | BdDp7XBdE7xRHvNzzoX7bBxsaEdsGtDMbprZQ19v4n5GFLGieqrDN/EOCtfNfXI9t1era9j4qcCF 253 | 05yhcMe7XJPRHb8JxOu2GE/2hRC5ALqTSdB1bu5l0iY7vlC1CuVzL4sYeynN5Zs5tzYS6ENrbptn 254 | pctBdYmtC8wpOnUPIqMXmUaB1xas+j6WKO6+90UTjflVgZCB0kOLtcv2pGV4FUQ+ULrRGtzgScao 255 | 2foVyReAjitkmfMlTMCAdEiLdkUiCuXTJNby1msPBXjobPl45H5RnWIlocIdRqTGnKGcF/lqA+uj 256 | uhoQle1XeAIp723oDkIWce5q9+io7bzb5joKzNxjWtTIqFSpzDzuE4gKbrKeNsTnFKVMvD3TbrKJ 257 | BVt9hKxiplVLjYEatxXr6glMsoQBFboQ/Nl95MwLdjogQ2Icri8t7OwUdX4jsUdthZE8DNYlGLSC 258 | aLttfAeNUEZUuCH1i87swh6OvEv0IkfUVjHc/AcMKHHDdfx2e4lFu/cKsZt813auiWkNDf7NUm8n 259 | pi3uM/K6S8ckzapKzk77TLlTOeodDM/ZWqP7DMPKs1uffOvcljqs7chro9IXPQH1aJ131b46Y+xl 260 | ztRpvaVWbENdbEsyvJCbyiLkGzJpTnxEySu4dujpuIo9QvHEoR5NdF1FD9CjE4ioPWxwDlv1Zt6l 261 | Dr8ev4guTyoNwqBryriDUXchwFNUzvEFksF9D/PLECkp1ZcNpV23tlZvutzj84DqzR2For7jchrS 262 | tK7jcyp0R447Tbuei5AV6+eJKzaK6+YH0XLZ9EJ1kSv4hnOZL5cDx22Uzj5vrEfct5HPxcWcGJMF 263 | 99ur0o+HGL/muZXK0Su6hN21DyjCIELYoesFpmbxOpwae7+btI0YBqTYb6hyIuhoY+YdOho6b8LB 264 | k0X14D97205tfOqzWuG2wwHkBiyk/dSdZMinqz1x4zgzEnVpP2iBq/KWeBQSkJAy8xpe511dakw4 265 | nitaOhfz1qxoKjhubhh7eweKHOvXlzh+wMlRqLMYH7KQmgOyG9bUBoUUNND6mHKSX6dRXEeGPU78 266 | Zd66z6BG06Oxr5Q/P3gS4vuCYyTTKdVv2i5R61kFjwE+xMi/9HI00MBJASHrSNAcaBQP9wpATVcD 267 | L0ovULwM4NAn7v4op0Esx62IMWQaq2k9q+0pkz3urKz6zDgY6+tZ6s8hmTdru5nEw9l2Mx+TVoZz 268 | tfN4ItevR6RVqrbvT0Vxh4opX/LBfZ+skDHVMGwzg/eKiCn0Jm103xSdMDxXqhK0vWSwzLEVeKuY 269 | NANB1bTOPSm3LrlaD95QiFLFdASxJh18Pz1k6WY/1juny0EUKAwxeyEwS3twIDU1VJq1L1gIXD8J 270 | UcoVk5zuuZLmnmeFmvhEFHwOFFqWPbuSwbtXafJRfo3KC9HcA/bPOc9Tx6TLAFBjoZt9SlvaITSI 271 | RKbJWeie8UA5BCjLUY1CmxQdtqKKlGZV0Yg9/N/HNpspjjje2EwGtZbcjVqb1Qs4e2m2NaDh1h5h 272 | rSQ2eNRqSuRbZjQ4jivyTUrCs8/3vcqr1gaDhWG279uuFk5T4FW0Uz3I60shvOc4Cff6ABhZtwpj 273 | K7t2Ws9FoICQuThUF4Rr5Zj4JQnjU48eKaRILXvOjbXF2Ju4DvUww/Di38iAoQgq6d25qMM6t7px 274 | hP14Wnlk/JCAl2Is5hPematoMMT0KW6J3li/YNAnTQqmEN9lfPPIpuBaUlZpghmyaUIbgJX2BH0k 275 | XnVpzy92UAyyY7k1YJ3kDmpWBOmTuwNBLiovPUmStAZfbC28Cg/LTeRff8VFF+APChjmif2NHYa7 276 | X3y4IUteH5JdjbKFZ4rFc7Uw1+oERQvxHpLkl6JGVZUupGGsbuskz5dSkShPvtjLNxQURYA+kmVM 277 | WqGilZM2u5VLP+i6ILonsZMUdY49feZfrD+sRicI7a6qPzqeFH4E0JdR1y81J4cElXj2RfwH2fZN 278 | 88bCdVV2qMo/w2XcfdPMUdK2X36qusuKbE7UCq1Q9WoTtIhRkk96eTlqX5HmefUyThvcWRxlMQ5X 279 | ugVZ2Xb2UlKwaNpgSHbloveO2mfON6ca/FmZcMzGJH6HhRP6N5MgfwVTa9/xbHkQJfcqjxPkJOPf 280 | LaM3Wb57t0KUBE8XBXWdxAuLsv0Kpgxqp9qgLN6Vb/bgInxhs7e7NxHeeGdwkfZtIrVZUK7enqz+ 281 | 4yHGQW5wTqS+q9LsbQq+zdvWCzOz/bNpVRWOs60/VE2M2Px62S8fi3+vhWuWl5ifaVmeE1j4My0n 282 | MrIg/kzLs/xK4H6qFUgo/nSswHAS/ZN5P9TUn2nXKq98acG33IFvZzCs4mnhs7ZJtKws3vWf1H7L 283 | 2J/CHwzxb7r3qMVX96MuBbeWSdDyWf83r+B/POxbxn6SsGU5DPTPnm1s820PLLHc7PkHrReKJLeU 284 | e+A2J9AfCLsdA4SlGN7eNjSDTRY2MPYksNj5hzv80X5xjXGPJ+HiLgkwPCyg+O6m+Px96976bumS 285 | H8DIWyxdSqa+4SjefW+MePvOyuSXOusiHCXNLaPA58u/m599CT47Ffy1oO+33wFQSwECPwMUAwAA 286 | CAD4NMlIEqnxGQgNAAC1DwAAEwAAAAAAAAAAACCApIEAAAAAY2xlYW5fd29yZGZpbGUuZG9jeFBL 287 | AQI/AxQDAAAIAM64dUg9zKdAuiMAAEY4AAAaAAAAAAAAAAAAIICkgTkNAAByZWNobnVuZy1wb3N0 288 | bWFzdGVyNzg5LmRvY1BLBQYAAAAAAgACAIkAAAArMQAAAAA= 289 | 290 | --b1_111612a42c23c082c8e459be9dccc04d-- 291 | -------------------------------------------------------------------------------- /test_mails/Dok1.doc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbidy/MacroMilter/015fff038686a159e7e6696c6fb3236f0847f449/test_mails/Dok1.doc -------------------------------------------------------------------------------- /test_mails/TestDoc.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbidy/MacroMilter/015fff038686a159e7e6696c6fb3236f0847f449/test_mails/TestDoc.zip -------------------------------------------------------------------------------- /test_mails/Test_Dock_Block.doc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbidy/MacroMilter/015fff038686a159e7e6696c6fb3236f0847f449/test_mails/Test_Dock_Block.doc -------------------------------------------------------------------------------- /test_mails/Test_Dock_Block.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbidy/MacroMilter/015fff038686a159e7e6696c6fb3236f0847f449/test_mails/Test_Dock_Block.zip -------------------------------------------------------------------------------- /test_mails/Test_Dock_Block_2x.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbidy/MacroMilter/015fff038686a159e7e6696c6fb3236f0847f449/test_mails/Test_Dock_Block_2x.zip -------------------------------------------------------------------------------- /test_mails/clean_wordfile.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbidy/MacroMilter/015fff038686a159e7e6696c6fb3236f0847f449/test_mails/clean_wordfile.docx -------------------------------------------------------------------------------- /test_mails/clean_wordfile.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbidy/MacroMilter/015fff038686a159e7e6696c6fb3236f0847f449/test_mails/clean_wordfile.zip -------------------------------------------------------------------------------- /test_mails/encrypted.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbidy/MacroMilter/015fff038686a159e7e6696c6fb3236f0847f449/test_mails/encrypted.zip -------------------------------------------------------------------------------- /test_mails/rechnung-postmaster789.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbidy/MacroMilter/015fff038686a159e7e6696c6fb3236f0847f449/test_mails/rechnung-postmaster789.zip -------------------------------------------------------------------------------- /test_mails/zipwithinfectedandnotinfectedword.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbidy/MacroMilter/015fff038686a159e7e6696c6fb3236f0847f449/test_mails/zipwithinfectedandnotinfectedword.zip -------------------------------------------------------------------------------- /travis/MacroMilter.conf: -------------------------------------------------------------------------------- 1 | description "MacroMilter Service" 2 | 3 | start on runlevel [234] 4 | stop on runlevel [0156] 5 | 6 | setuid travis 7 | setgid travis 8 | 9 | # automatically respawn: 10 | respawn 11 | respawn limit 99 5 12 | 13 | script 14 | chdir /usr/bin/ 15 | exec sudo /usr/bin/python macromilter.py | logger -t MacroMilter 16 | end script 17 | 18 | -------------------------------------------------------------------------------- /travis/config_1.ini: -------------------------------------------------------------------------------- 1 | # TEST - Default 2 | [Milter] 3 | # at postfix smtpd_milters = inet:127.0.0.1:3690 4 | # bind to unix or tcp socket "inet:port@ip" or "///.sock" 5 | SOCKET = inet:10025@127.0.0.1 6 | # Set umask for unix socket, e.g. 0077 for group writable 7 | UMASK = 0077 8 | # Milter timout in seconds 9 | TIMEOUT = 30 10 | # Define the max size for each message in bytes (~50MB) 11 | MAX_FILESIZE = 50000000 12 | # Reject error message 13 | MESSAGE = ERROR - Attachment contains unallowed office macros! 14 | # Reject the mail if a malware macro is detected (yes/no) 15 | REJECT_MESSAGE = yes 16 | # Max nested archive depth - recommendation = 5 17 | MAX_ZIP = 5 18 | # If the message will be rejected - dump the mail body to file? 19 | DUMP_BODY = no 20 | 21 | [Logging] 22 | LOGFILE_DIR = /home/travis/ 23 | LOGFILE_NAME = macromilter.log 24 | # Loglevels are: 1 = Debug (default) , 2 = Info, 3 = Warning/Error 25 | LOGLEVEL = 1 26 | 27 | [Whitelist] 28 | # Add (comma separated json format) some whitelisted recipients or sender to the list to skip the VBA parsing ["xyz@example.de","test@test.de"] 29 | Recipients = ["",""] 30 | # Add a SHA256 hash from the macro code - to obtain these hash please see in the log for "INFO: [ID] The macro hash is: [..]XYZ[..]" 31 | # example: 05357f85049ba05fb9c7cdc9c6e979b0cb9db600a78eaf98a39344db2f6a6473 32 | # Please define as json: ["hash#1","hash#2"] 33 | Macrohash = [] 34 | -------------------------------------------------------------------------------- /travis/config_2.ini: -------------------------------------------------------------------------------- 1 | # TEST -- MacroHash 2 | [Milter] 3 | # at postfix smtpd_milters = inet:127.0.0.1:3690 4 | # bind to unix or tcp socket "inet:port@ip" or "///.sock" 5 | SOCKET = inet:10025@127.0.0.1 6 | # Set umask for unix socket, e.g. 0077 for group writable 7 | UMASK = 0077 8 | # Milter timout in seconds 9 | TIMEOUT = 30 10 | # Define the max size for each message in bytes (~50MB) 11 | MAX_FILESIZE = 50000000 12 | # Reject error message 13 | MESSAGE = ERROR - Attachment contains unallowed office macros! 14 | # Reject the mail if a malware macro is detected (yes/no) 15 | REJECT_MESSAGE = yes 16 | # Max nested archive depth - recommendation = 5 17 | MAX_ZIP = 5 18 | # If the message will be rejected - dump the mail body to file? 19 | DUMP_BODY = no 20 | 21 | [Logging] 22 | LOGFILE_DIR = /home/travis/ 23 | LOGFILE_NAME = macromilter.log 24 | # Loglevels are: 1 = Debug (default) , 2 = Info, 3 = Warning/Error 25 | LOGLEVEL = 1 26 | 27 | [Whitelist] 28 | # Add (comma separated json format) some whitelisted recipients or sender to the list to skip the VBA parsing ["xyz@example.de","test@test.de"] 29 | Recipients = [] 30 | # Add a SHA256 hash from the macro code - to obtain these hash please see in the log for "INFO: [ID] The macro hash is: [..]XYZ[..]" 31 | # example: 05357f85049ba05fb9c7cdc9c6e979b0cb9db600a78eaf98a39344db2f6a6473 32 | # Please define as json: ["hash#1","hash#2"] 33 | Macrohash = ["c4884f2c7e330712c273f414afff684686d0390928ecca4424127c6818ceee82"] 34 | -------------------------------------------------------------------------------- /travis/config_3.ini: -------------------------------------------------------------------------------- 1 | # TEST -- REJECT = NO 2 | [Milter] 3 | # at postfix smtpd_milters = inet:127.0.0.1:3690 4 | # bind to unix or tcp socket "inet:port@ip" or "///.sock" 5 | SOCKET = inet:10025@127.0.0.1 6 | # Set umask for unix socket, e.g. 0077 for group writable 7 | UMASK = 0077 8 | # Milter timout in seconds 9 | TIMEOUT = 30 10 | # Define the max size for each message in bytes (~50MB) 11 | MAX_FILESIZE = 50000000 12 | # Reject error message 13 | MESSAGE = ERROR - Attachment contains unallowed office macros! 14 | # Reject the mail if a malware macro is detected (yes/no) 15 | REJECT_MESSAGE = no 16 | # Max nested archive depth - recommendation = 5 17 | MAX_ZIP = 5 18 | # If the message will be rejected - dump the mail body to file? 19 | DUMP_BODY = no 20 | 21 | [Logging] 22 | LOGFILE_DIR = /home/travis/ 23 | LOGFILE_NAME = macromilter.log 24 | # Loglevels are: 1 = Debug (default) , 2 = Info, 3 = Warning/Error 25 | LOGLEVEL = 1 26 | 27 | [Whitelist] 28 | # Add (comma separated json format) some whitelisted recipients or sender to the list to skip the VBA parsing ["xyz@example.de","test@test.de"] 29 | Recipients = [] 30 | # Add a SHA256 hash from the macro code - to obtain these hash please see in the log for "INFO: [ID] The macro hash is: [..]XYZ[..]" 31 | # example: 05357f85049ba05fb9c7cdc9c6e979b0cb9db600a78eaf98a39344db2f6a6473 32 | # Please define as json: ["hash#1","hash#2"] 33 | Macrohash = [] 34 | -------------------------------------------------------------------------------- /travis/mailalias.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | sudo chmod 777 /home/travis/build/sbidy/MacroMilter/travis/readmail.py 3 | sudo chown travis:travis /home/travis/build/sbidy/MacroMilter/travis/readmail.py 4 | sudo echo "test: |/home/travis/build/sbidy/MacroMilter/travis/readmail.py >> /home/travis/build/sbidy/MacroMilter/travis/mail.txt" >> /etc/aliases 5 | sudo newaliases 6 | sudo cat /etc/aliases 7 | -------------------------------------------------------------------------------- /travis/readmail.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import email 3 | import sys 4 | 5 | for line in sys.stdin.read().split('\n'): 6 | print(line) 7 | 8 | -------------------------------------------------------------------------------- /travis/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # create files and folders 3 | mkdir /etc/macromilter/ 4 | mkdir -p /var/log/macromilter/ 5 | # only needed for a chroot env 6 | # mkdir /var/spool/postfix/etc/milter 7 | 8 | # copy the python script 9 | cp ./macromilter/macromilter.py /usr/bin/ 10 | cp ./macromilter/config.ini /etc/macromilter/ 11 | 12 | # setup upstart config 13 | cp ./macromilter/MacroMilter.conf /etc/init/ 14 | initctl reload-configuration 15 | 16 | # set chown for postfix 17 | chown postfix:postfix -R /etc/macromilter 18 | chown postfix:postfix -R /var/log/macromilter 19 | chown postfix:postfix /usr/bin/macromilter.py 20 | 21 | # start and check 22 | service MacroMilter start 23 | tail /var/log/macromilter/macromilter.log -------------------------------------------------------------------------------- /travis/tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ! sendemail -f travis@localhost -t travis@localhost -m "test" -s localhost -u "test" -a ./test_mails/Test_Dock_Block.doc -v 3 | -------------------------------------------------------------------------------- /travis/tests_zip.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ! sendemail -f travis@localhost -t travis@localhost -m "test" -s localhost -u "test" -a ./test_mails/Test_Dock_Block_2x.zip -v 3 | --------------------------------------------------------------------------------