View | Details | Raw Unified
Collapse All | Expand All

(-) Mailman/Utils.py (-20 / +184 lines)
 Lines 1-4    Link Here 
# 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
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# modify it under the terms of the GNU General Public License
 Lines 159-165    Link Here 
        doc.AddItem('<hr>')
        doc.AddItem('<hr>')
        return
        return
    code = cgi_info['html_code'].value
    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)
    langdir = os.path.join(mlist.fullpath(), mlist.preferred_language)
    # Make sure the directory exists
    # Make sure the directory exists
    omask = os.umask(0)
    omask = os.umask(0)
 Lines 1-4    Link Here 
# 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
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# modify it under the terms of the GNU General Public License
 Lines 436-452    Link Here 
            # Convert any html entities to Unicode
            # Convert any html entities to Unicode
            mlist.subject_prefix = Utils.canonstr(
            mlist.subject_prefix = Utils.canonstr(
                val, mlist.preferred_language)
                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
        else:
        else:
            GUIBase._setValue(self, mlist, property, val, doc)
            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):
    def _postValidate(self, mlist, doc):
        if not mlist.reply_to_address.strip() and \
        if not mlist.reply_to_address.strip() and \
 Lines 1-4    Link Here 
# Copyright (C) 2002-2004 by the Free Software Foundation, Inc.
# Copyright (C) 2002-2007 by the Free Software Foundation, Inc.
#
#
# This program is free software; you can redistribute it and/or
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# modify it under the terms of the GNU General Public License
 Lines 12-18    Link Here 
#
#
# You should have received a copy of the GNU General Public License
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
# USA.
"""Base class for all web GUI components."""
"""Base class for all web GUI components."""
 Lines 122-131    Link Here 
        # Validate all the attributes for this category
        # Validate all the attributes for this category
        pass
        pass
    def _escape(self, property, value):
        value = value.replace('<', '&lt;')
        return value
    def handleForm(self, mlist, category, subcat, cgidata, doc):
    def handleForm(self, mlist, category, subcat, cgidata, doc):
        for item in self.GetConfigInfo(mlist, category, subcat):
        for item in self.GetConfigInfo(mlist, category, subcat):
            # Skip descriptions and legacy non-attributes
            # Skip descriptions and legacy non-attributes
 Lines 144-153    Link Here 
            elif not cgidata.has_key(property):
            elif not cgidata.has_key(property):
                continue
                continue
            elif isinstance(cgidata[property], ListType):
            elif isinstance(cgidata[property], ListType):
                val = [self._escape(property, x.value)
                val = [x.value for x in cgidata[property]]
                       for x in cgidata[property]]
            else:
            else:
                val = self._escape(property, cgidata[property].value)
                val = cgidata[property].value
            # Coerce the value to the expected type, raising exceptions if the
            # Coerce the value to the expected type, raising exceptions if the
            # value is invalid.
            # value is invalid.
            try:
            try:
 Lines 876-878    Link Here 
    except (LookupError, UnicodeError, ValueError, HeaderParseError):
    except (LookupError, UnicodeError, ValueError, HeaderParseError):
        # possibly charset problem. return with undecoded string in one line.
        # possibly charset problem. return with undecoded string in one line.
        return EMPTYSTRING.join(s.splitlines())
        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