Edgewall Software

Changeset 10788


Ignore:
Timestamp:
Aug 26, 2011, 12:29:32 PM (12 years ago)
Author:
Jun Omae
Message:

0.12.3dev: Fixed XSS vulnerabilities based on CSS with IE and Firefox 3.6. HTML sanitizer uses a whitelist CSS properties.

Location:
branches/0.12-stable/trac/util
Files:
1 added
2 edited

Legend:

Unmodified
Added
Removed
  • branches/0.12-stable/trac/util/html.py

    r10753 r10788  
    2626class TracHTMLSanitizer(HTMLSanitizer):
    2727
    28     UNSAFE_CSS = set([
    29         'position',
    30         # IE <http://trac.edgewall.org/ticket/10114>
    31         'behavior',
    32         # Opera <http://trac.edgewall.org/ticket/10115>
    33         '-o-link', '-o-link-source',
     28    SAFE_CSS = frozenset([
     29        # CSS 3 properties <http://www.w3.org/TR/CSS/#properties>
     30        'background', 'background-attachment', 'background-color',
     31        'background-image', 'background-position', 'background-repeat',
     32        'border', 'border-bottom', 'border-bottom-color',
     33        'border-bottom-style', 'border-bottom-width', 'border-collapse',
     34        'border-color', 'border-left', 'border-left-color',
     35        'border-left-style', 'border-left-width', 'border-right',
     36        'border-right-color', 'border-right-style', 'border-right-width',
     37        'border-spacing', 'border-style', 'border-top', 'border-top-color',
     38        'border-top-style', 'border-top-width', 'border-width', 'bottom',
     39        'caption-side', 'clear', 'clip', 'color', 'content',
     40        'counter-increment', 'counter-reset', 'cursor', 'direction', 'display',
     41        'empty-cells', 'float', 'font', 'font-family', 'font-size',
     42        'font-style', 'font-variant', 'font-weight', 'height', 'left',
     43        'letter-spacing', 'line-height', 'list-style', 'list-style-image',
     44        'list-style-position', 'list-style-type', 'margin', 'margin-bottom',
     45        'margin-left', 'margin-right', 'margin-top', 'max-height', 'max-width',
     46        'min-height', 'min-width', 'opacity', 'orphans', 'outline',
     47        'outline-color', 'outline-style', 'outline-width', 'overflow',
     48        'padding', 'padding-bottom', 'padding-left', 'padding-right',
     49        'padding-top', 'page-break-after', 'page-break-before',
     50        'page-break-inside', 'position', 'quotes', 'right', 'table-layout',
     51        'text-align', 'text-decoration', 'text-indent', 'text-transform',
     52        'top', 'unicode-bidi', 'vertical-align', 'visibility', 'white-space',
     53        'widows', 'width', 'word-spacing', 'z-index',
    3454    ])
    3555
    36     def __init__(self, safe_schemes=HTMLSanitizer.SAFE_SCHEMES):
     56    def __init__(self, safe_schemes=HTMLSanitizer.SAFE_SCHEMES,
     57                 safe_css=SAFE_CSS):
    3758        safe_attrs = HTMLSanitizer.SAFE_ATTRS | frozenset(['style'])
    3859        safe_schemes = frozenset(safe_schemes)
    3960        super(TracHTMLSanitizer, self).__init__(safe_attrs=safe_attrs,
    4061                                                safe_schemes=safe_schemes)
     62        self.safe_css = frozenset(safe_css)
     63
     64    # IE6 <http://heideri.ch/jso/#80>
     65    _EXPRESSION_SEARCH = re.compile(u"""
     66        [eE
     67         \uFF25 # FULLWIDTH LATIN CAPITAL LETTER E
     68         \uFF45 # FULLWIDTH LATIN SMALL LETTER E
     69        ]
     70        [xX
     71         \uFF38 # FULLWIDTH LATIN CAPITAL LETTER X
     72         \uFF58 # FULLWIDTH LATIN SMALL LETTER X
     73        ]
     74        [pP
     75         \uFF30 # FULLWIDTH LATIN CAPITAL LETTER P
     76         \uFF50 # FULLWIDTH LATIN SMALL LETTER P
     77        ]
     78        [rR
     79         \u0280 # LATIN LETTER SMALL CAPITAL R
     80         \uFF32 # FULLWIDTH LATIN CAPITAL LETTER R
     81         \uFF52 # FULLWIDTH LATIN SMALL LETTER R
     82        ]
     83        [eE
     84         \uFF25 # FULLWIDTH LATIN CAPITAL LETTER E
     85         \uFF45 # FULLWIDTH LATIN SMALL LETTER E
     86        ]
     87        [sS
     88         \uFF33 # FULLWIDTH LATIN CAPITAL LETTER S
     89         \uFF53 # FULLWIDTH LATIN SMALL LETTER S
     90        ]{2}
     91        [iI
     92         \u026A # LATIN LETTER SMALL CAPITAL I
     93         \uFF29 # FULLWIDTH LATIN CAPITAL LETTER I
     94         \uFF49 # FULLWIDTH LATIN SMALL LETTER I
     95        ]
     96        [oO
     97         \uFF2F # FULLWIDTH LATIN CAPITAL LETTER O
     98         \uFF4F # FULLWIDTH LATIN SMALL LETTER O
     99        ]
     100        [nN
     101         \u0274 # LATIN LETTER SMALL CAPITAL N
     102         \uFF2E # FULLWIDTH LATIN CAPITAL LETTER N
     103         \uFF4E # FULLWIDTH LATIN SMALL LETTER N
     104        ]
     105        """, re.VERBOSE).search
     106
     107    # IE6 <http://openmya.hacker.jp/hasegawa/security/expression.txt>
     108    #     7) Particular bit of Unicode characters
     109    _URL_FINDITER = re.compile(
     110        u'[Uu][Rr\u0280][Ll\u029F]\s*\(([^)]+)').finditer
    41111
    42112    def sanitize_css(self, text):
     
    54124                continue
    55125            is_evil = False
    56             if 'expression' in decl:
     126            if self._EXPRESSION_SEARCH(decl):
    57127                is_evil = True
    58             for match in re.finditer(r'url\s*\(([^)]+)', decl):
     128            for match in self._URL_FINDITER(decl):
    59129                if not self.is_safe_uri(match.group(1)):
    60130                    is_evil = True
     
    87157        considered safe for inclusion in the output.
    88158        """
    89         if prop in self.UNSAFE_CSS:
     159        if prop not in self.safe_css:
    90160            return False
     161        # Position can be used for phishing, 'static' excepted
     162        if prop == 'position':
     163            return value.lower() == 'static'
    91164        # Negative margins can be used for phishing
    92         elif prop.startswith('margin') and '-' in value:
    93             return False
     165        if prop.startswith('margin'):
     166            return '-' not in value
    94167        return True
     168
     169    _NORMALIZE_NEWLINES = re.compile(r'\r\n').sub
     170    _UNICODE_ESCAPE = re.compile(
     171        r"""\\([0-9a-fA-F]{1,6})\s?|\\([^\r\n\f0-9a-fA-F'"{};:()#*])""",
     172        re.UNICODE).sub
     173
     174    def _replace_unicode_escapes(self, text):
     175        def _repl(match):
     176            t = match.group(1)
     177            if t:
     178                return unichr(int(t, 16))
     179            t = match.group(2)
     180            if t == '\\':
     181                return r'\\'
     182            else:
     183                return t
     184        return self._UNICODE_ESCAPE(_repl,
     185                                    self._NORMALIZE_NEWLINES('\n', text))
    95186
    96187
  • branches/0.12-stable/trac/util/tests/__init__.py

    r10115 r10788  
    1919
    2020from trac import util
    21 from trac.util.tests import concurrency, datefmt, presentation, text
     21from trac.util.tests import concurrency, datefmt, presentation, text, html
    2222
    2323
     
    146146    suite.addTest(doctest.DocTestSuite(util))
    147147    suite.addTest(text.suite())
     148    suite.addTest(html.suite())
    148149    return suite
    149150
Note: See TracChangeset for help on using the changeset viewer.