aboutsummaryrefslogtreecommitdiffstats
path: root/Mailman
diff options
context:
space:
mode:
authorMark Sapiro <msapiro@value.net>2007-12-04 11:52:18 -0800
committerMark Sapiro <msapiro@value.net>2007-12-04 11:52:18 -0800
commit18f080804e368c63de499b32717d57701aaf8880 (patch)
treeb9f33705aaf602cc16984f9e4c87c04ec12777bb /Mailman
parent23c33ffc04737e21135743bdb7c56921966176a6 (diff)
downloadmailman2-18f080804e368c63de499b32717d57701aaf8880.tar.gz
mailman2-18f080804e368c63de499b32717d57701aaf8880.tar.xz
mailman2-18f080804e368c63de499b32717d57701aaf8880.zip
Mailman/Cgi/edithtml.py
Mailman/Gui/General.py Mailman/Utils.py - Better detection of potentially evil HTML in GUI. Mailman/Version.py NEWS - Updates for 2.1.10b1 release. Mailman/Gui/General.py messages/mailman.pot - Added admin_member_chunksize to Gui. Two new associated messages.
Diffstat (limited to 'Mailman')
-rw-r--r--Mailman/Cgi/edithtml.py17
-rw-r--r--Mailman/Gui/General.py32
-rw-r--r--Mailman/Utils.py151
-rw-r--r--Mailman/Version.py6
4 files changed, 192 insertions, 14 deletions
diff --git a/Mailman/Cgi/edithtml.py b/Mailman/Cgi/edithtml.py
index b5967b34..3aa8ab4e 100644
--- a/Mailman/Cgi/edithtml.py
+++ b/Mailman/Cgi/edithtml.py
@@ -1,4 +1,4 @@
-# Copyright (C) 1998-2006 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2007 by the Free Software Foundation, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -159,7 +159,20 @@ def ChangeHTML(mlist, cgi_info, template_name, doc):
doc.AddItem('<hr>')
return
code = cgi_info['html_code'].value
- code = re.sub(r'<([/]?script.*?)>', r'&lt;\1&gt;', code)
+ if Utils.suspiciousHTML(code):
+ doc.AddItem(Header(3,
+ _("""The page you saved contains suspicious HTML that could
+potentially expose your users to cross-site scripting attacks. This change
+has therefore been rejected. If you still want to make these changes, you
+must have shell access to your Mailman server.
+ """)))
+ doc.AddItem(_('See '))
+ doc.AddItem(Link(
+'http://www.python.org/cgi-bin/faqw-mm.py?req=show&file=faq04.048.htp',
+ _('FAQ 4.48.')))
+ doc.AddItem(Header(3,_("Page Unchanged.")))
+ doc.AddItem('<hr>')
+ return
langdir = os.path.join(mlist.fullpath(), mlist.preferred_language)
# Make sure the directory exists
omask = os.umask(0)
diff --git a/Mailman/Gui/General.py b/Mailman/Gui/General.py
index 6b03fd2c..8271a30e 100644
--- a/Mailman/Gui/General.py
+++ b/Mailman/Gui/General.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2001-2006 by the Free Software Foundation, Inc.
+# Copyright (C) 2001-2007 by the Free Software Foundation, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
@@ -19,6 +19,8 @@
import re
+from types import IntType
+
from Mailman import mm_cfg
from Mailman import Utils
from Mailman import Errors
@@ -358,6 +360,10 @@ class General(GUIBase):
_('''Maximum length in kilobytes (KB) of a message body. Use 0
for no limit.''')),
+ ('admin_member_chunksize', mm_cfg.Number, 7, 0,
+ _('''Maximum number of members to show on one page of the
+ Membership List.''')),
+
('host_name', mm_cfg.Host, WIDTH, 0,
_('Host name this list prefers for email.'),
@@ -436,17 +442,25 @@ class General(GUIBase):
# Convert any html entities to Unicode
mlist.subject_prefix = Utils.canonstr(
val, mlist.preferred_language)
+ elif property == 'info':
+ if val <> mlist.info:
+ if Utils.suspiciousHTML(val):
+ doc.addError(_("""The <b>info</b> attribute you saved
+contains suspicious HTML that could potentially expose your users to cross-site
+scripting attacks. This change has therefore been rejected. If you still want
+to make these changes, you must have shell access to your Mailman server.
+This change can be made with bin/withlist or with bin/config_list by setting
+mlist.info.
+ """))
+ else:
+ mlist.info = val
+ elif property == 'admin_member_chunksize' and (val < 1
+ or not isinstance(val, IntType)):
+ doc.addError(_("""<b>admin_member_chunksize</b> attribute not
+ changed! It must be an integer > 0."""))
else:
GUIBase._setValue(self, mlist, property, val, doc)
- def _escape(self, property, value):
- # The 'info' property allows HTML, but let's sanitize it to avoid XSS
- # exploits. Everything else should be fully escaped.
- if property <> 'info':
- return GUIBase._escape(self, property, value)
- # Sanitize <script> and </script> tags but nothing else. Not the best
- # solution, but expedient.
- return re.sub(r'(?i)<([/]?script.*?)>', r'&lt;\1&gt;', value)
def _postValidate(self, mlist, doc):
if not mlist.reply_to_address.strip() and \
diff --git a/Mailman/Utils.py b/Mailman/Utils.py
index 2641875c..7b2cf439 100644
--- a/Mailman/Utils.py
+++ b/Mailman/Utils.py
@@ -876,3 +876,154 @@ def oneline(s, cset):
except (LookupError, UnicodeError, ValueError, HeaderParseError):
# possibly charset problem. return with undecoded string in one line.
return EMPTYSTRING.join(s.splitlines())
+
+
+# Patterns and functions to flag possible XSS attacks in HTML.
+# This list is compiled from information at http://ha.ckers.org/xss.html,
+# http://www.quirksmode.org/js/events_compinfo.html,
+# http://www.htmlref.com/reference/appa/events1.htm,
+# http://lxr.mozilla.org/mozilla/source/content/events/src/nsDOMEvent.cpp#59,
+# http://www.w3.org/TR/DOM-Level-2-Events/events.html and
+# http://www.xulplanet.com/references/elemref/ref_EventHandlers.html
+# Many thanks are due to Moritz Naumann for his assistance with this.
+_badwords = [
+ '<i?frame',
+ '<link',
+ '<meta',
+ '<script',
+ r'(?:^|\W)j(?:ava)?script(?:\W|$)',
+ r'(?:^|\W)vbs(?:cript)?(?:\W|$)',
+ r'(?:^|\W)domactivate(?:\W|$)',
+ r'(?:^|\W)domattrmodified(?:\W|$)',
+ r'(?:^|\W)domcharacterdatamodified(?:\W|$)',
+ r'(?:^|\W)domfocus(?:in|out)(?:\W|$)',
+ r'(?:^|\W)dommenuitem(?:in)?active(?:\W|$)',
+ r'(?:^|\W)dommousescroll(?:\W|$)',
+ r'(?:^|\W)domnodeinserted(?:intodocument)?(?:\W|$)',
+ r'(?:^|\W)domnoderemoved(?:fromdocument)?(?:\W|$)',
+ r'(?:^|\W)domsubtreemodified(?:\W|$)',
+ r'(?:^|\W)fscommand(?:\W|$)',
+ r'(?:^|\W)onabort(?:\W|$)',
+ r'(?:^|\W)on(?:de)?activate(?:\W|$)',
+ r'(?:^|\W)on(?:after|before)print(?:\W|$)',
+ r'(?:^|\W)on(?:after|before)update(?:\W|$)',
+ r'(?:^|\W)onbefore(?:(?:de)?activate|copy|cut|editfocus|paste)(?:\W|$)',
+ r'(?:^|\W)onbeforeunload(?:\W|$)',
+ r'(?:^|\W)onbegin(?:\W|$)',
+ r'(?:^|\W)onblur(?:\W|$)',
+ r'(?:^|\W)onbounce(?:\W|$)',
+ r'(?:^|\W)onbroadcast(?:\W|$)',
+ r'(?:^|\W)on(?:cell)?change(?:\W|$)',
+ r'(?:^|\W)oncheckboxstatechange(?:\W|$)',
+ r'(?:^|\W)on(?:dbl)?click(?:\W|$)',
+ r'(?:^|\W)onclose(?:\W|$)',
+ r'(?:^|\W)oncommand(?:update)?(?:\W|$)',
+ r'(?:^|\W)oncomposition(?:end|start)(?:\W|$)',
+ r'(?:^|\W)oncontextmenu(?:\W|$)',
+ r'(?:^|\W)oncontrolselect(?:\W|$)',
+ r'(?:^|\W)oncopy(?:\W|$)',
+ r'(?:^|\W)oncut(?:\W|$)',
+ r'(?:^|\W)ondataavailable(?:\W|$)',
+ r'(?:^|\W)ondataset(?:changed|complete)(?:\W|$)',
+ r'(?:^|\W)ondrag(?:drop|end|enter|exit|gesture|leave|over)?(?:\W|$)',
+ r'(?:^|\W)ondragstart(?:\W|$)',
+ r'(?:^|\W)ondrop(?:\W|$)',
+ r'(?:^|\W)onend(?:\W|$)',
+ r'(?:^|\W)onerror(?:update)?(?:\W|$)',
+ r'(?:^|\W)onfilterchange(?:\W|$)',
+ r'(?:^|\W)onfinish(?:\W|$)',
+ r'(?:^|\W)onfocus(?:in|out)?(?:\W|$)',
+ r'(?:^|\W)onhelp(?:\W|$)',
+ r'(?:^|\W)oninput(?:\W|$)',
+ r'(?:^|\W)onkey(?:up|down|press)(?:\W|$)',
+ r'(?:^|\W)onlayoutcomplete(?:\W|$)',
+ r'(?:^|\W)on(?:un)?load(?:\W|$)',
+ r'(?:^|\W)onlosecapture(?:\W|$)',
+ r'(?:^|\W)onmedia(?:complete|error)(?:\W|$)',
+ r'(?:^|\W)onmouse(?:down|enter|leave|move|out|over|up|wheel)(?:\W|$)',
+ r'(?:^|\W)onmove(?:end|start)?(?:\W|$)',
+ r'(?:^|\W)on(?:off|on)line(?:\W|$)',
+ r'(?:^|\W)onoutofsync(?:\W|$)',
+ r'(?:^|\W)onoverflow(?:changed)?(?:\W|$)',
+ r'(?:^|\W)onpage(?:hide|show)(?:\W|$)',
+ r'(?:^|\W)onpaint(?:\W|$)',
+ r'(?:^|\W)onpaste(?:\W|$)',
+ r'(?:^|\W)onpause(?:\W|$)',
+ r'(?:^|\W)onpopup(?:hidden|hiding|showing|shown)(?:\W|$)',
+ r'(?:^|\W)onprogress(?:\W|$)',
+ r'(?:^|\W)onpropertychange(?:\W|$)',
+ r'(?:^|\W)onradiostatechange(?:\W|$)',
+ r'(?:^|\W)onreadystatechange(?:\W|$)',
+ r'(?:^|\W)onrepeat(?:\W|$)',
+ r'(?:^|\W)onreset(?:\W|$)',
+ r'(?:^|\W)onresize(?:end|start)?(?:\W|$)',
+ r'(?:^|\W)onresume(?:\W|$)',
+ r'(?:^|\W)onreverse(?:\W|$)',
+ r'(?:^|\W)onrow(?:delete|enter|exit|inserted)(?:\W|$)',
+ r'(?:^|\W)onrows(?:delete|enter|inserted)(?:\W|$)',
+ r'(?:^|\W)onscroll(?:\W|$)',
+ r'(?:^|\W)onseek(?:\W|$)',
+ r'(?:^|\W)onselect(?:start)?(?:\W|$)',
+ r'(?:^|\W)onselectionchange(?:\W|$)',
+ r'(?:^|\W)onstart(?:\W|$)',
+ r'(?:^|\W)onstop(?:\W|$)',
+ r'(?:^|\W)onsubmit(?:\W|$)',
+ r'(?:^|\W)onsync(?:from|to)preference(?:\W|$)',
+ r'(?:^|\W)onsyncrestored(?:\W|$)',
+ r'(?:^|\W)ontext(?:\W|$)',
+ r'(?:^|\W)ontimeerror(?:\W|$)',
+ r'(?:^|\W)ontrackchange(?:\W|$)',
+ r'(?:^|\W)onunderflow(?:\W|$)',
+ r'(?:^|\W)onurlflip(?:\W|$)',
+ r'(?:^|\W)seeksegmenttime(?:\W|$)',
+ r'(?:^|\W)svgabort(?:\W|$)',
+ r'(?:^|\W)svgerror(?:\W|$)',
+ r'(?:^|\W)svgload(?:\W|$)',
+ r'(?:^|\W)svgresize(?:\W|$)',
+ r'(?:^|\W)svgscroll(?:\W|$)',
+ r'(?:^|\W)svgunload(?:\W|$)',
+ r'(?:^|\W)svgzoom(?:\W|$)',
+ ]
+
+
+# This is the actual re to look for the above patterns
+_badhtml = re.compile('|'.join(_badwords), re.IGNORECASE)
+# This is used to filter non-printable us-ascii characters, some of which
+# can be used to break words to avoid recognition.
+_filterchars = re.compile('[\000-\011\013\014\016-\037\177-\237]')
+# This is used to recognize '&#' and '%xx' strings for _translate which
+# translates them to characters
+_encodedchars = re.compile('(&#[0-9]+;?)|(&#x[0-9a-f]+;?)|(%[0-9a-f]{2})',
+ re.IGNORECASE)
+
+
+def _translate(mo):
+ """Translate &#... and %xx encodings into the encoded character."""
+ match = mo.group().lower().strip('&#;')
+ try:
+ if match.startswith('x') or match.startswith('%'):
+ val = int(match[1:], 16)
+ else:
+ val = int(match, 10)
+ except ValueError:
+ return ''
+ if val < 256:
+ return chr(val)
+ else:
+ return ''
+
+
+def suspiciousHTML(html):
+ """Check HTML string for various tags, script language names and
+ 'onxxx' actions that can be used in XSS attacks.
+ Currently, this a very simple minded test. It just looks for
+ patterns without analyzing context. Thus, it potentially flags lots
+ of benign stuff.
+ Returns True if anything suspicious found, False otherwise.
+ """
+
+ if _badhtml.search(_filterchars.sub(
+ '', _encodedchars.sub(_translate, html))):
+ return True
+ else:
+ return False
diff --git a/Mailman/Version.py b/Mailman/Version.py
index e45020ad..907370da 100644
--- a/Mailman/Version.py
+++ b/Mailman/Version.py
@@ -16,7 +16,7 @@
# USA.
# Mailman version
-VERSION = "2.1.10a0"
+VERSION = "2.1.10b1"
# And as a hex number in the manner of PY_VERSION_HEX
ALPHA = 0xa
@@ -29,9 +29,9 @@ FINAL = 0xf
MAJOR_REV = 2
MINOR_REV = 1
MICRO_REV = 10
-REL_LEVEL = ALPHA
+REL_LEVEL = BETA
# at most 15 beta releases!
-REL_SERIAL = 0
+REL_SERIAL = 1
HEX_VERSION = ((MAJOR_REV << 24) | (MINOR_REV << 16) | (MICRO_REV << 8) |
(REL_LEVEL << 4) | (REL_SERIAL << 0))