Merge lp:~mhall119/summit/xss-vulnerability-fix into lp:~summit-hackers/summit/1.x

Proposed by Michael Hall
Status: Merged
Approved by: Nigel Babu
Approved revision: 144
Merged at revision: 144
Proposed branch: lp:~mhall119/summit/xss-vulnerability-fix
Merge into: lp:~summit-hackers/summit/1.x
Diff against target: 193 lines (+166/-2)
2 files modified
summit/sponsor/templates/sponsor/review.html (+1/-1)
summit/sponsor/tests.py (+165/-1)
To merge this branch: bzr merge lp:~mhall119/summit/xss-vulnerability-fix
Reviewer Review Type Date Requested Status
Nigel Babu (community) Approve
Review via email: mp+73091@code.launchpad.net

Description of the change

Overview
========
Sponsorship application data was being displayed unescaped, allowing javascript injection into the pages.

Details
=======
Django will escape data being inserted into a template by default, but due to a combination of template filters the sponsorship.about field was having it's unescaped data inserted, which allowed for someone to submit a sponsorship application containing embedded javascript that would be executed by any user viewing their application. This MP contains tests for specifically this behavior, and changes the order of filters being applied so that the final result will still be properly escaped.

To post a comment you must log in.
Revision history for this message
Nigel Babu (nigelbabu) wrote :

Ack. Good catch. I'm tempted to Rick Roll someone before we merge it in though :P

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'summit/sponsor/templates/sponsor/review.html'
2--- summit/sponsor/templates/sponsor/review.html 2010-10-03 22:43:07 +0000
3+++ summit/sponsor/templates/sponsor/review.html 2011-08-26 19:16:19 +0000
4@@ -17,7 +17,7 @@
5 </table>
6
7 <p>
8-{{ sponsorship.about|escape|paras|urlize }}
9+{{ sponsorship.about|escape|urlize|paras }}
10 </p>
11
12 <!--
13
14=== modified file 'summit/sponsor/tests.py'
15--- summit/sponsor/tests.py 2011-08-22 16:12:36 +0000
16+++ summit/sponsor/tests.py 2011-08-26 19:16:19 +0000
17@@ -23,6 +23,8 @@
18 from django.contrib.auth.models import User
19 from summit.schedule.models import Summit
20 from summit.sponsor.models import (
21+ Sponsorship,
22+ SponsorshipScore,
23 SponsorshipSuggestion,
24 SponsorshipSuggestionScore,
25 NonLaunchpadSponsorship,
26@@ -147,5 +149,167 @@
27 }
28 form = SponsorshipSuggestionForm(form_data)
29 self.assertFalse(form.is_valid())
30-
31+
32+
33+class SponsorshipReviewTestCase(djangotest.TestCase):
34+
35+ def setUp(self):
36+ self.summit = Summit.objects.create(
37+ name='test-summit',
38+ title='Test Summit',
39+ location='Test Location',
40+ description='Test Summit Description',
41+ timezone='UTC',
42+ last_update=datetime.datetime.now(),
43+ state='sponsor',
44+ date_start=(datetime.datetime.now() + datetime.timedelta(days=1)),
45+ date_end=(datetime.datetime.now() + datetime.timedelta(days=6)),
46+ )
47+
48+ self.user = User.objects.create(
49+ username='testuser',
50+ first_name='Test',
51+ last_name='User',
52+ )
53+
54+ self.sponsorship = Sponsorship.objects.create(
55+ user=self.user,
56+ summit=self.summit,
57+ location='Test Location',
58+ country='Test Country',
59+ about='Test About Description',
60+ needs_travel=False,
61+ needs_accomodation=False,
62+ would_crew=True,
63+ diet='Test Diet',
64+ further_info='Test Further Info',
65+ )
66+
67+ self.scorer = User.objects.create(
68+ username='testscorer',
69+ first_name='Test',
70+ last_name='Scorer',
71+ is_superuser=True,
72+ )
73+ self.scorer.set_password('password')
74+ self.scorer.save()
75+
76+ self.score = SponsorshipScore.objects.create(
77+ sponsorship=self.sponsorship,
78+ user=self.scorer,
79+ score=1,
80+ comment='Test Comment',
81+ )
82+
83+ def test_sponsorship_review_display(self):
84+ self.client.login(username='testscorer', password='password')
85+ response = self.client.get('/test-summit/sponsorship/review/%s' % self.sponsorship.id)
86+ self.assertContains(response, '<h1>Test User</h1>', 1)
87+ self.assertContains(response, '<th>Location:</th><td>Test Location</td>', 1)
88+ self.assertContains(response, '<th>Country:</th><td>Test Country</td>', 1)
89+ self.assertContains(response, '<p>\nTest About Description\n</p>', 1)
90+
91+ def test_sponsorship_about_paras_filtering(self):
92+ self.sponsorship.about = 'Test\nAbout\nDescription'
93+ self.sponsorship.save()
94+
95+ self.client.login(username='testscorer', password='password')
96+ response = self.client.get('/test-summit/sponsorship/review/%s' % self.sponsorship.id)
97+ self.assertContains(response, '<p>\nTest</p>\n<p>\nAbout</p>\n<p>\nDescription\n</p>', 1)
98+
99+ def test_sponsorship_about_urlize_filtering(self):
100+ self.sponsorship.about = 'Test http://example.org Description'
101+ self.sponsorship.save()
102+
103+ self.client.login(username='testscorer', password='password')
104+ response = self.client.get('/test-summit/sponsorship/review/%s' % self.sponsorship.id)
105+ self.assertContains(response, '<p>\nTest <a href="http://example.org" rel="nofollow">http://example.org</a> Description\n</p>', 1)
106+
107+ def test_sponsorship_xss_escaping(self):
108+ self.sponsorship.location = '"><script>alert(/xss/)</script>'
109+ self.sponsorship.country = '"><script>alert(/xss/)</script>'
110+ self.sponsorship.about = '"><script>alert(/xss/)</script>'
111+ self.sponsorship.save()
112+
113+ self.user.first_name= '"><script>alert(/xss/)</script>'
114+ self.user.last_name=''
115+ self.user.save()
116+
117+ self.client.login(username='testscorer', password='password')
118+ response = self.client.get('/test-summit/sponsorship/review/%s' % self.sponsorship.id)
119+ # All displayed fields should have their html escaped
120+ self.assertContains(response, '<h1>&quot;&gt;&lt;script&gt;alert(/xss/)&lt;/script&gt;</h1>', 1)
121+ self.assertContains(response, '<th>Location:</th><td>&quot;&gt;&lt;script&gt;alert(/xss/)&lt;/script&gt;</td>', 1)
122+ self.assertContains(response, '<th>Country:</th><td>&quot;&gt;&lt;script&gt;alert(/xss/)&lt;/script&gt;</td>', 1)
123+ self.assertContains(response, '"><script>alert(/xss/)</script>', 0)
124+
125+
126+class SuggestionReviewTestCase(djangotest.TestCase):
127+
128+ def setUp(self):
129+ self.summit = Summit.objects.create(
130+ name='test-summit',
131+ title='Test Summit',
132+ location='Test Location',
133+ description='Test Summit Description',
134+ timezone='UTC',
135+ last_update=datetime.datetime.now(),
136+ state='sponsor',
137+ date_start=(datetime.datetime.now() + datetime.timedelta(days=1)),
138+ date_end=(datetime.datetime.now() + datetime.timedelta(days=6)),
139+ )
140+
141+ self.user = User.objects.create(
142+ username='testuser',
143+ first_name='Test',
144+ last_name='User',
145+ )
146+
147+ self.sponsorship = SponsorshipSuggestion.objects.create(
148+ suggested_by=self.user,
149+ name='Test Suggestion',
150+ launchpad_name='testsuggestion',
151+ summit=self.summit,
152+ location='Test Location',
153+ country='Test Country',
154+ about='Test About Description',
155+ needs_travel=False,
156+ needs_accomodation=False,
157+ would_crew=True,
158+ diet='Test Diet',
159+ further_info='Test Further Info',
160+ )
161+
162+ self.scorer = User.objects.create(
163+ username='testscorer',
164+ first_name='Test',
165+ last_name='Scorer',
166+ is_superuser=True,
167+ )
168+ self.scorer.set_password('password')
169+ self.scorer.save()
170+
171+ self.score = SponsorshipSuggestionScore.objects.create(
172+ sponsorship=self.sponsorship,
173+ user=self.scorer,
174+ score=1,
175+ comment='Test Comment',
176+ )
177+
178+ def test_sponsorshipsuggestion_review_display(self):
179+ self.client.login(username='testscorer', password='password')
180+ response = self.client.get('/test-summit/suggestedsponsorship/review/%s' % self.sponsorship.id)
181+ self.assertContains(response, '<h1>Test Suggestion</h1>', 1)
182+
183+ def test_sponsorshipsuggestion_xss_escaping(self):
184+ self.client.login(username='testscorer', password='password')
185+ self.sponsorship.name = '"><script>alert(/xss/)</script>'
186+ self.sponsorship.location = '"><script>alert(/xss/)</script>'
187+ self.sponsorship.country = '"><script>alert(/xss/)</script>'
188+ self.sponsorship.about = '"><script>alert(/xss/)</script>'
189+ self.sponsorship.save()
190+ response = self.client.get('/test-summit/suggestedsponsorship/review/%s' % self.sponsorship.id)
191+ # All displayed fields should have their html escaped
192+ self.assertContains(response, '"><script>alert(/xss/)</script>', 0)
193+
194

Subscribers

People subscribed via source and target branches

to all changes: