Re: [courier-users] Pythonfilter attachments

2017-07-25 Thread Gordon Messmer

On 07/25/2017 09:53 AM, Alessandro Vesely wrote:

I've published my alternative version here:
https://www.tana.it/sw/pythonfilter_attachments/



I'll take a look at that shortly.  Thanks.


--
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
___
courier-users mailing list
courier-users@lists.sourceforge.net
Unsubscribe: https://lists.sourceforge.net/lists/listinfo/courier-users


Re: [courier-users] Pythonfilter attachments

2017-07-25 Thread Alessandro Vesely
On Wed 08/Feb/2017 22:11:53 +0100 Gordon Messmer wrote:
> On 02/08/2017 10:24 AM, Alessandro Vesely wrote:
>> I revamped attachments.py in order to catch Javascript Trojans inside 
>> a zip, which were driving me crazy.
> 
> The current version supports libarchive, which should allow you to 
> blacklist file types inside zip files, as well.

Yup, that's right.  I hadn't got it.  I re-introduced support for libarchive, 
and have been using my alternative version since then.  Today I added the .ace 
extension, after I found a Trojan-PSW.Win32.Fareit.cxcl wrapped that way.

It may be safer to just use all available filters.  However, the original 
attachments.py fails like so:

Initialized the "attachments" python filter
Traceback (most recent call last):
  File 
"../courier-pythonfilter/courier-pythonfilter-1.11/filters/attachments.py", 
line 111, in 
print doFilter(sys.argv[1], [])
  File 
"../courier-pythonfilter/courier-pythonfilter-1.11/filters/attachments.py", 
line 90, in doFilter
if filename and checkArchive(filename, part):
  File 
"../courier-pythonfilter/courier-pythonfilter-1.11/filters/attachments.py", 
line 52, in checkArchive
if fparts[-1].lower() in libarchive.FILTERS:
AttributeError: 'module' object has no attribute 'FILTERS'

If I patch it as attached, it throws no exception, but doesn't block an .exe 
inside an .ace either.  Indeed, in python, I see .ace is not set:

Python 2.7.9 (default, Jun 29 2016, 13:08:31) 
[GCC 4.9.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import libarchive
>>> libarchive.ffi.READ_FILTER
Traceback (most recent call last):
  File "", line 1, in 
AttributeError: 'module' object has no attribute 'READ_FILTER'
>>> libarchive.ffi.READ_FILTERS
set([u'lzma', u'all', u'uu', u'lzop', u'compress', u'bzip2', u'lzip', u'xz', 
u'lrzip', u'gzip', u'grzip', u'rpm', u'none'])
>>> libarchive.ffi.READ_FORMATS
set([u'all', u'zip', u'tar', u'lha', u'iso9660', u'7zip', u'xar', u'mtree', 
u'cpio', u'raw', u'ar', u'rar', u'cab', u'empty'])
>>> 

I've published my alternative version here:
https://www.tana.it/sw/pythonfilter_attachments/

Ale
-- 


--- ../courier-pythonfilter/courier-pythonfilter-1.11/filters/attachments.py
2016-05-05 06:08:01.0 +0200
+++ attachments.py  2017-07-25 18:34:55.003804899 +0200
@@ -49,9 +49,9 @@
 if not haveLibarchive:
 return False
 fparts = filename.split('.')
-if fparts[-1].lower() in libarchive.FILTERS:
+if fparts[-1].lower() in libarchive.ffi.READ_FILTERS:
 fparts.pop()
-if fparts[-1].lower() not in libarchive.FORMATS:
+if fparts[-1].lower() not in libarchive.ffi.READ_FORMATS:
 return False
 d = tempfile.mkdtemp()
 f = '%s/%s' % (d, filename.replace('/',''))
--
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot___
courier-users mailing list
courier-users@lists.sourceforge.net
Unsubscribe: https://lists.sourceforge.net/lists/listinfo/courier-users


Re: [courier-users] Pythonfilter attachments

2017-02-08 Thread SZÉPE Viktor
Idézem/Quoting Gordon Messmer :

> On 02/08/2017 10:24 AM, Alessandro Vesely wrote:
>> I revamped attachments.py in order to catch Javascript Trojans inside
>> a zip, which were driving me crazy.
>
>
> The current version supports libarchive, which should allow you to
> blacklist file types inside zip files, as well.

Could you mention it in the config file?
https://github.com/szepeviktor/courier-pythonfilter/blob/master/pythonfilter.conf#L84

Thanks.



SZÉPE Viktor
https://github.com/szepeviktor/debian-server-tools/blob/master/CV.md
-- 
+36-20-4242498  s...@szepe.net  skype: szepe.viktor
Budapest, III. kerület





--
Check out the vibrant tech community on one of the world's most
engaging tech sites, SlashDot.org! http://sdm.link/slashdot
___
courier-users mailing list
courier-users@lists.sourceforge.net
Unsubscribe: https://lists.sourceforge.net/lists/listinfo/courier-users


Re: [courier-users] Pythonfilter attachments

2017-02-08 Thread Gordon Messmer
On 02/08/2017 10:24 AM, Alessandro Vesely wrote:
> I revamped attachments.py in order to catch Javascript Trojans inside 
> a zip, which were driving me crazy.


The current version supports libarchive, which should allow you to 
blacklist file types inside zip files, as well.


--
Check out the vibrant tech community on one of the world's most
engaging tech sites, SlashDot.org! http://sdm.link/slashdot
___
courier-users mailing list
courier-users@lists.sourceforge.net
Unsubscribe: https://lists.sourceforge.net/lists/listinfo/courier-users


[courier-users] Pythonfilter attachments

2017-02-08 Thread Alessandro Vesely

Hi all,

I revamped attachments.py in order to catch Javascript Trojans inside a zip, 
which were driving me crazy.  While I added that, I removed the configurable 
archive.  The attached flavor of the filter rejects just the extensions 
hardcoded in the source.


Enjoy
Ale
#!/usr/bin/python
# attachments -- Courier filter which blocks specified attachment types
# Copyright (C) 2005-2008  Robert Penz 
# hacked (H) 2017 ale
#
# This file is part of pythonfilter.
#
# pythonfilter is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pythonfilter is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with pythonfilter.  If not, see .

import sys
from email.message import Message, _unquotevalue
import email.utils
from zipfile import ZipFile
from StringIO import StringIO

# https://support.google.com/mail/answer/6590?hl=en
# http://www.theverge.com/2017/1/25/14391462/gmail-javascript-block-file-attachments-malware-security
blocked_extensions = ('.js',
	'.ade', '.adp', '.bat', '.chm', '.cmd', '.com', '.cpl', '.exe', '.hta',
	'.ins', '.isp', '.jar', '.jse', '.lib', '.lnk', '.mde', '.msc', '.msp',
	'.mst', '.pif', '.scr', '.sct', '.shb', '.sys', '.vb', '.vbe', '.vbs',
	'.vxd', '.wsc', '.wsf', '.wsh')


def de_comment(field):
	"""Parse a header field fragment and remove comments.

	copied from AddrlistClass.getdelimited() in email/_parseaddr.py
	"""

	slist = ['']
	quote = False
	pos = 0
	depth = 0
	while pos < len(field):
		if quote:
			quote = False
		elif field[pos] == '(':
			depth += 1
		elif field[pos] == ')':
			depth = max(depth - 1, 0)
			pos += 1
			continue
		elif field[pos] == '\\':
			quote = True
		if depth == 0:
			slist.append(field[pos])
		pos += 1

	return ''.join(slist)

def is_quoted(value):
	""" Check whether a value (string or tuple) is quoted
	"""
	if isinstance(value, tuple):
		return value[2].startswith('"')
	else:
		return value.startswith('"')

class MyMessage(Message):
	"""Email message with comments stripped
	"""

	def __init__(self, *args, **kwargs):
		Message.__init__(self, *args, **kwargs)

	def get_filename(self, failobj=None):
		"""Return the filename associated with the payload if present.

		The filename is extracted from the Content-Disposition header's
		`filename' parameter.  If that header is missing the `filename'
		parameter, this method falls back to looking for the `name' parameter.
		"""
		# changed from original: get the unquoted string
		missing = object()
		filename = self.get_param('filename', missing, 'content-disposition',
			unquote=False)
		if filename is missing:
			filename = self.get_param('name', missing, 'content-type', unquote=False)
		if filename is missing:
			return failobj

		# added to original: non quoted comments are removed
		bare = is_quoted(filename)
		if not bare:
			filename = _unquotevalue(filename)
		filename = email.utils.collapse_rfc2231_value(filename)
		if bare and '(' in filename:
			filename = de_comment(filename)
		return filename.strip().lower()

def test_fname(filename):
	""" filename defined and lower().strip()
	"""
	if filename.endswith(".gz"):
		filename = filename[0:len(filename)-3]

	return filename.endswith(blocked_extensions)

def doFilter(bodyFile, controlFileList):
	try:
		msg = email.message_from_file(open(bodyFile), _class=MyMessage)
		block = False

		for part in msg.walk():
			try:

# multipart/* are just containers
if part.get_content_maintype() == 'multipart':
	continue

# get_filename() is subclassed
filename = part.get_filename()

if filename:
	if filename.endswith(".zip"):
		mzip = ZipFile(StringIO(part.get_payload(decode=True)))
		for fn in mzip.namelist():
			block = test_fname(fn)
			if block:
break
	else:
		block = test_fname(filename)

			except:
continue

			if block:
return "550 Attachment rejected for policy reasons"

	except Exception as e:
		sys.stderr.write('attachments ' + type(e).__name__ + ': ' + str(e) + '\n')

	# nothing found --> to the next filter
	return ''


if __name__ == '__main__':
	# For debugging, you can create a file that contains a message
	# body, possibly including attachments.
	# Run this script with the name of that file as an argument,
	# and it'll print either a permanent failure code to indicate
	# that the message would be rejected, or print nothing to
	# indicate that the remaining filters would be run.
	if len(sys.argv) != 2:
		print "Usage: attachments.py "
		sys.exit(0)
	print doFilter(sys.argv[1], [])