comparison test/test_cgi.py @ 4880:ca692423e401

Different approach to fix XSS in issue2550817 Encapsulate the error/ok message append method as add_ok_message and add_error_message. The new approach escapes the messages when appending -- at a point in the code where we still know where the message comes from. Escaping is the default but can bei turned off. This also fixes issue2550836 where certain messages may contain links. Another advantage of the new fix is that users don't need to change installed trackers and are secure by default.
author Ralf Schlatterbeck <rsc@runtux.com>
date Mon, 31 Mar 2014 18:19:23 +0200
parents 24b8011cd2dc
children 3b9252085ba9
comparison
equal deleted inserted replaced
4879:302c967d710c 4880:ca692423e401
38 form.list.append(x) 38 form.list.append(x)
39 else: 39 else:
40 form.list.append(cgi.MiniFieldStorage(k, v)) 40 form.list.append(cgi.MiniFieldStorage(k, v))
41 return form 41 return form
42 42
43 cm = client.clean_message 43 cm = client.add_message
44 class MessageTestCase(unittest.TestCase): 44 class MessageTestCase(unittest.TestCase):
45 # Note: We used to allow some html tags in error message. Now *only* 45 # Note: Escaping is now handled on a message-by-message basis at a
46 # newlines are allowed and messages are split at newlines. 46 # point where we still know what generates a message. In this way we
47 # Note that tags are no longer escaped, see doc/upgrading.txt for 47 # can decide when to escape and when not. We test the add_message
48 # the changes needed in the templates (Migrating from 1.5.0 to 1.5.1) 48 # routine here.
49 def testCleanMessageOK(self): 49 # Of course we won't catch errors in judgement when to escape here
50 self.assertEqual(cm('a'), ['a']) 50 # -- but at the time of this change only one message is not escaped.
51 self.assertEqual(cm('a\nb'), ['a','b']) 51 def testAddMessageOK(self):
52 self.assertEqual(cm('a\nb\nc\n'), ['a','b','c','']) 52 self.assertEqual(cm([],'a\nb'), ['a<br />\nb'])
53 self.assertEqual(cm([],'a\nb\nc\n'), ['a<br />\nb<br />\nc<br />\n'])
54
55 def testAddMessageBAD(self):
56 self.assertEqual(cm([],'<script>x</script>'),
57 ['&lt;script&gt;x&lt;/script&gt;'])
58 self.assertEqual(cm([],'<iframe>x</iframe>'),
59 ['&lt;iframe&gt;x&lt;/iframe&gt;'])
60 self.assertEqual(cm([],'<<script >>alert(42);5<</script >>'),
61 ['&lt;&lt;script &gt;&gt;alert(42);5&lt;&lt;/script &gt;&gt;'])
62 self.assertEqual(cm([],'<a href="y">x</a>'),
63 ['&lt;a href="y"&gt;x&lt;/a&gt;'])
64 self.assertEqual(cm([],'<A HREF="y">x</A>'),
65 ['&lt;A HREF="y"&gt;x&lt;/A&gt;'])
66 self.assertEqual(cm([],'<br>x<br />'), ['&lt;br&gt;x&lt;br /&gt;'])
67 self.assertEqual(cm([],'<i>x</i>'), ['&lt;i&gt;x&lt;/i&gt;'])
68 self.assertEqual(cm([],'<b>x</b>'), ['&lt;b&gt;x&lt;/b&gt;'])
69 self.assertEqual(cm([],'<BR>x<BR />'), ['&lt;BR&gt;x&lt;BR /&gt;'])
70 self.assertEqual(cm([],'<I>x</I>'), ['&lt;I&gt;x&lt;/I&gt;'])
71 self.assertEqual(cm([],'<B>x</B>'), ['&lt;B&gt;x&lt;/B&gt;'])
72
73 def testAddMessageNoEscape(self):
74 self.assertEqual(cm([],'<i>x</i>',False), ['<i>x</i>'])
75 self.assertEqual(cm([],'<i>x</i>\n<b>x</b>',False),
76 ['<i>x</i><br />\n<b>x</b>'])
53 77
54 class FormTestCase(unittest.TestCase): 78 class FormTestCase(unittest.TestCase):
55 def setUp(self): 79 def setUp(self):
56 self.dirname = '_test_cgi_form' 80 self.dirname = '_test_cgi_form'
57 # set up and open a tracker 81 # set up and open a tracker
670 if nodeid is not None: 694 if nodeid is not None:
671 cl.nodeid = nodeid 695 cl.nodeid = nodeid
672 cl.db = self.db 696 cl.db = self.db
673 cl.userid = userid 697 cl.userid = userid
674 cl.language = ('en',) 698 cl.language = ('en',)
675 cl.error_message = [] 699 cl._error_message = []
676 cl.template = template 700 cl.template = template
677 return cl 701 return cl
678 702
679 def testClassPermission(self): 703 def testClassPermission(self):
680 cl = self._make_client(dict(username='bob')) 704 cl = self._make_client(dict(username='bob'))
741 cl = self._make_client({'username':'new_user', 'password':'secret', 765 cl = self._make_client({'username':'new_user', 'password':'secret',
742 '@confirm@password':'secret', 'address':'new_user@bork.bork', 766 '@confirm@password':'secret', 'address':'new_user@bork.bork',
743 'roles':'Admin'}, nodeid=None, userid='4') 767 'roles':'Admin'}, nodeid=None, userid='4')
744 self.assertRaises(exceptions.Unauthorised, 768 self.assertRaises(exceptions.Unauthorised,
745 actions.NewItemAction(cl).handle) 769 actions.NewItemAction(cl).handle)
746 self.assertEqual(cl.error_message,[]) 770 self.assertEqual(cl._error_message,[])
747 # this should work 771 # this should work
748 cl = self._make_client({'username':'new_user', 'password':'secret', 772 cl = self._make_client({'username':'new_user', 'password':'secret',
749 '@confirm@password':'secret', 'address':'new_user@bork.bork'}, 773 '@confirm@password':'secret', 'address':'new_user@bork.bork'},
750 nodeid=None, userid='4') 774 nodeid=None, userid='4')
751 self.assertRaises(exceptions.Redirect, 775 self.assertRaises(exceptions.Redirect,
752 actions.NewItemAction(cl).handle) 776 actions.NewItemAction(cl).handle)
753 self.assertEqual(cl.error_message,[]) 777 self.assertEqual(cl._error_message,[])
754 # don't allow changing (my own) username (in this example) 778 # don't allow changing (my own) username (in this example)
755 cl = self._make_client(dict(username='new_user42'), userid='4') 779 cl = self._make_client(dict(username='new_user42'), userid='4')
756 self.assertRaises(exceptions.Unauthorised, 780 self.assertRaises(exceptions.Unauthorised,
757 actions.EditItemAction(cl).handle) 781 actions.EditItemAction(cl).handle)
758 cl = self._make_client(dict(username='new_user42'), userid='4', 782 cl = self._make_client(dict(username='new_user42'), userid='4',
887 self.assertEqual([x.id for x in h.batch()],['1', '2', '3']) 911 self.assertEqual([x.id for x in h.batch()],['1', '2', '3'])
888 912
889 def testEditCSV(self): 913 def testEditCSV(self):
890 form = dict(rows='id,name\n1,newkey') 914 form = dict(rows='id,name\n1,newkey')
891 cl = self._make_client(form, userid='1', classname='keyword') 915 cl = self._make_client(form, userid='1', classname='keyword')
892 cl.ok_message = [] 916 cl._ok_message = []
893 actions.EditCSVAction(cl).handle() 917 actions.EditCSVAction(cl).handle()
894 self.assertEqual(cl.ok_message, ['Items edited OK']) 918 self.assertEqual(cl._ok_message, ['Items edited OK'])
895 k = self.db.keyword.getnode('1') 919 k = self.db.keyword.getnode('1')
896 self.assertEqual(k.name, 'newkey') 920 self.assertEqual(k.name, 'newkey')
897 form = dict(rows=u'id,name\n1,\xe4\xf6\xfc'.encode('utf-8')) 921 form = dict(rows=u'id,name\n1,\xe4\xf6\xfc'.encode('utf-8'))
898 cl = self._make_client(form, userid='1', classname='keyword') 922 cl = self._make_client(form, userid='1', classname='keyword')
899 cl.ok_message = [] 923 cl._ok_message = []
900 actions.EditCSVAction(cl).handle() 924 actions.EditCSVAction(cl).handle()
901 self.assertEqual(cl.ok_message, ['Items edited OK']) 925 self.assertEqual(cl._ok_message, ['Items edited OK'])
902 k = self.db.keyword.getnode('1') 926 k = self.db.keyword.getnode('1')
903 self.assertEqual(k.name, u'\xe4\xf6\xfc'.encode('utf-8')) 927 self.assertEqual(k.name, u'\xe4\xf6\xfc'.encode('utf-8'))
904 928
905 def testRoles(self): 929 def testRoles(self):
906 cl = self._make_client({}) 930 cl = self._make_client({})

Roundup Issue Tracker: http://roundup-tracker.org/