From: alecm <svn...@pl...> - 2007-04-28 13:57:38
|
Author: alecm Date: Sat Apr 28 13:57:32 2007 New Revision: 7974 Added: PortalTransforms/branches/1.5/tests/test_xss.py Modified: PortalTransforms/branches/1.5/HISTORY.txt PortalTransforms/branches/1.5/transforms/safe_html.py Log: Add XSS fixes from Anton Stonor to safe_html transform Modified: PortalTransforms/branches/1.5/HISTORY.txt ============================================================================== --- PortalTransforms/branches/1.5/HISTORY.txt (original) +++ PortalTransforms/branches/1.5/HISTORY.txt Sat Apr 28 13:57:32 2007 @@ -1,3 +1,9 @@ +1.5.2 - Unreleased +================== + + * Add XSS fixes from Anton Stonor to safe_html transform. + [alecm, stonor] + 1.5.1-final - 2007-04-17 ======================== Added: PortalTransforms/branches/1.5/tests/test_xss.py ============================================================================== --- (empty file) +++ PortalTransforms/branches/1.5/tests/test_xss.py Sat Apr 28 13:57:32 2007 @@ -0,0 +1,150 @@ +""" +""" +import os, sys +if __name__ == '__main__': + execfile(os.path.join(sys.path[0], 'framework.py')) + +from Testing import ZopeTestCase +from Products.Archetypes.tests.atsitetestcase import ATSiteTestCase + + +class TestXSSFilter(ATSiteTestCase): + + def afterSetUp(self): + ATSiteTestCase.afterSetUp(self) + self.engine = self.portal.portal_transforms + + def doTest(self, data_in, data_out): + html = self.engine.convertTo('text/x-html-safe', data_in, mimetype="text/html") + assert(html.getData()) + self.assertEqual (data_out,html.getData()) + + + + def test_1(self): + data_in = """<html><body><img src="javascript:Alert('XSS');" /></body></html>""" + data_out = """<img />""" + self.doTest(data_in, data_out) + + def test_2(self): + data_in = """<img src="javascript:Alert('XSS');" />""" + data_out = """<img />""" + self.doTest(data_in, data_out) + + def test_3(self): + data_in = """<html><body><IMG SRC=javascript:alert('XSS')></body></html>""" + data_out = """<img />""" + self.doTest(data_in, data_out) + + def test_4(self): + data_in = """<IMG SRC=javascript:alert('XSS')>""" + data_out = """<img />""" + + self.doTest(data_in, data_out) + + def test_5(self): + data_in = """<img src="jav + asc + ript:Alert('XSS');" />""" + data_out = """<img />""" + self.doTest(data_in, data_out) + + + def test_6(self): + data_in = """<img src="jav asc ript:Alert('XSS');"/>""" + data_out = """<img />""" + self.doTest(data_in, data_out) + + def test_7(self): + data_in = """<a href=javascript:alert('XSS')>test med a-tag</a>""" + data_out = """<a>test med a-tag</a>""" + self.doTest(data_in, data_out) + + def test_8(self): + data_in = """<div style="bacground:url(jav asc ript:Alert('XSS')">test</div>""" + data_out = """<div>test</div>""" + self.doTest(data_in, data_out) + + def test_9(self): + data_in = """<div style="bacground:url(jav + asc + ript: + Alert('XSS')">test</div>""" + data_out = """<div>test</div>""" + self.doTest(data_in, data_out) + + def test_10(self): + data_in = """<div style="bacground:url(javascript:alert('XSS')">test</div>""" + data_out = """<div>test</div>""" + self.doTest(data_in, data_out) + + def test_11(self): + data_in = """<div style="bacground:url(v b sc ript:msgbox('XSS')">test</div>""" + data_out = """<div>test</div>""" + self.doTest(data_in, data_out) + + def test_12(self): + data_in = """<img src="vbscript:msgbox('XSS')"/>""" + data_out = """<img />""" + self.doTest(data_in, data_out) + + def test_13(self): + data_in = """<img src="vb + sc + ript:msgbox('XSS')"/>""" + data_out = """<img />""" + self.doTest(data_in, data_out) + + def test_14(self): + data_in = """<a href="vbscript:Alert('XSS')">test</a>""" + data_out = """<a>test</a>""" + self.doTest(data_in, data_out) + + def test_15(self): + data_in = """<div STYLE="width: expression(window.location='http://www.dr.dk';);">div</div>""" + data_out = """<div>div</div>""" + self.doTest(data_in, data_out) + + def test_16(self): + data_in = """<div STYLE="width: ex pre ss io n(window.location='http://www.dr.dk';);">div</div>""" + data_out = """<div>div</div>""" + self.doTest(data_in, data_out) + + def test_17(self): + data_in = """<div STYLE="width: ex + pre + ss + io + n(window.location='http://www.dr.dk';);">div</div>""" + data_out = """<div>div</div>""" + self.doTest(data_in, data_out) + + def test_18(self): + data_in = """<div style="width: 14px;">div</div>""" + data_out = data_in + self.doTest(data_in, data_out) + + def test_19(self): + data_in = """<a href="http://www.headnet.dk">headnet</a>""" + data_out = data_in + self.doTest(data_in, data_out) + + def test_20(self): + data_in = """<img src="http://www.headnet.dk/log.jpg" />""" + data_out = data_in + self.doTest(data_in, data_out) + + def test_21(self): + data_in = """<mustapha name="mustap" tlf="11 11 11 11" address="unknown">bla bla bla</mustapha>""" + data_out = """bla bla bla""" + self.doTest(data_in, data_out) + + +def test_suite(): + from unittest import TestSuite, makeSuite + suite = TestSuite() + suite.addTest(makeSuite(TestXSSFilter)) + return suite + +if __name__ == '__main__': + framework() Modified: PortalTransforms/branches/1.5/transforms/safe_html.py ============================================================================== --- PortalTransforms/branches/1.5/transforms/safe_html.py (original) +++ PortalTransforms/branches/1.5/transforms/safe_html.py Sat Apr 28 13:57:32 2007 @@ -1,5 +1,6 @@ import logging from sgmllib import SGMLParser +import re from Products.PortalTransforms.interfaces import itransform from Products.PortalTransforms.utils import log @@ -27,6 +28,33 @@ %s</d> """ +def hasScript(s): + """ Dig out evil Java/VB script inside an HTML attribute """ + + # look for "script" and "expression" + javascript_pattern = re.compile("([\s\n]*?s[\s\n]*?c[\s\n]*?r[\s\n]*?i[\s\n]*?p[\s\n]*?t[\s\n]*?:)|([\s\n]*?e[\s\n]*?x[\s\n]*?p[\s\n]*?r[\s\n]*?e[\s\n]*?s[\s\n]*?s[\s\n]*?i[\s\n]*?o[\s\n]*?n)", re.DOTALL|re.IGNORECASE) + s = decode_htmlentities(s) + return javascript_pattern.findall(s) + +def decode_htmlentities(s): + """ XSS code can be hidden with htmlentities """ + + entity_pattern = re.compile("&#(?P<htmlentity>x?\w+)?;?") + s = entity_pattern.sub(decode_htmlentity,s) + return s + +def decode_htmlentity(m): + entity_value = m.groupdict()['htmlentity'] + if entity_value.lower().startswith('x'): + try: + return chr(int('0'+entity_value,16)) + except ValueError: + return entity_value + try: + return chr(int(entity_value)) + except ValueError: + return entity_value + class StrippingParser(SGMLParser): """Pass only allowed tags; raise exception for known-bad. @@ -75,18 +103,19 @@ """ if self.suppress: return - #if self.remove_javascript and tag == script and : if self.valid.has_key(tag): self.result.append('<' + tag) + remove_script = getattr(self,'remove_javascript',True) + for k, v in attrs: - if self.remove_javascript and k.strip().lower().startswith('on'): + if remove_script and k.strip().lower().startswith('on'): if not self.raise_error: continue - else: raise IllegalHTML, 'Javascript event "%s" not allowed.' % k - elif self.remove_javascript and v.strip().lower().startswith('javascript:' ): + else: raise IllegalHTML, 'Script event "%s" not allowed.' % k + elif remove_script and hasScript(v): if not self.raise_error: continue - else: raise IllegalHTML, 'Javascript URI "%s" not allowed.' % v + else: raise IllegalHTML, 'Script URI "%s" not allowed.' % v else: self.result.append(' %s="%s"' % (k, v)) |