mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-01-24 08:14:16 +03:00
Fix XSS caused by disabled autoescaping in the default DRF Browsable API view templates (#6330)
* Add test that verifies that HTML is correctly escaped in Browsable API views * Fix `urlize_quoted_links` tag to avoid double escaping in autoescape mode * Fix XSS in default DRF Browsable API template by re-enabling autoescape
This commit is contained in:
parent
e3bd4b9048
commit
4bb9a3c484
|
@ -171,10 +171,10 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="response-info" aria-label="{% trans "response info" %}">
|
<div class="response-info" aria-label="{% trans "response info" %}">
|
||||||
<pre class="prettyprint"><span class="meta nocode"><b>HTTP {{ response.status_code }} {{ response.status_text }}</b>{% autoescape off %}{% for key, val in response_headers|items %}
|
<pre class="prettyprint"><span class="meta nocode"><b>HTTP {{ response.status_code }} {{ response.status_text }}</b>{% for key, val in response_headers|items %}
|
||||||
<b>{{ key }}:</b> <span class="lit">{{ val|break_long_headers|urlize_quoted_links }}</span>{% endfor %}
|
<b>{{ key }}:</b> <span class="lit">{{ val|break_long_headers|urlize_quoted_links }}</span>{% endfor %}
|
||||||
|
|
||||||
</span>{{ content|urlize_quoted_links }}</pre>{% endautoescape %}
|
</span>{{ content|urlize_quoted_links }}</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -336,6 +336,12 @@ def urlize_quoted_links(text, trim_url_limit=None, nofollow=True, autoescape=Tru
|
||||||
return limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x
|
return limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x
|
||||||
|
|
||||||
safe_input = isinstance(text, SafeData)
|
safe_input = isinstance(text, SafeData)
|
||||||
|
|
||||||
|
# Unfortunately, Django built-in cannot be used here, because escaping
|
||||||
|
# is to be performed on words, which have been forcibly coerced to text
|
||||||
|
def conditional_escape(text):
|
||||||
|
return escape(text) if autoescape and not safe_input else text
|
||||||
|
|
||||||
words = word_split_re.split(force_text(text))
|
words = word_split_re.split(force_text(text))
|
||||||
for i, word in enumerate(words):
|
for i, word in enumerate(words):
|
||||||
if '.' in word or '@' in word or ':' in word:
|
if '.' in word or '@' in word or ':' in word:
|
||||||
|
@ -376,21 +382,15 @@ def urlize_quoted_links(text, trim_url_limit=None, nofollow=True, autoescape=Tru
|
||||||
# Make link.
|
# Make link.
|
||||||
if url:
|
if url:
|
||||||
trimmed = trim_url(middle)
|
trimmed = trim_url(middle)
|
||||||
if autoescape and not safe_input:
|
lead, trail = conditional_escape(lead), conditional_escape(trail)
|
||||||
lead, trail = escape(lead), escape(trail)
|
url, trimmed = conditional_escape(url), conditional_escape(trimmed)
|
||||||
url, trimmed = escape(url), escape(trimmed)
|
|
||||||
middle = '<a href="%s"%s>%s</a>' % (url, nofollow_attr, trimmed)
|
middle = '<a href="%s"%s>%s</a>' % (url, nofollow_attr, trimmed)
|
||||||
words[i] = mark_safe('%s%s%s' % (lead, middle, trail))
|
words[i] = '%s%s%s' % (lead, middle, trail)
|
||||||
else:
|
else:
|
||||||
if safe_input:
|
words[i] = conditional_escape(word)
|
||||||
words[i] = mark_safe(word)
|
else:
|
||||||
elif autoescape:
|
words[i] = conditional_escape(word)
|
||||||
words[i] = escape(word)
|
return mark_safe(''.join(words))
|
||||||
elif safe_input:
|
|
||||||
words[i] = mark_safe(word)
|
|
||||||
elif autoescape:
|
|
||||||
words[i] = escape(word)
|
|
||||||
return ''.join(words)
|
|
||||||
|
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
|
|
|
@ -305,6 +305,15 @@ class URLizerTests(TestCase):
|
||||||
'"foo_set": [\n "<a href="http://api/foos/1/">http://api/foos/1/</a>"\n], '
|
'"foo_set": [\n "<a href="http://api/foos/1/">http://api/foos/1/</a>"\n], '
|
||||||
self._urlize_dict_check(data)
|
self._urlize_dict_check(data)
|
||||||
|
|
||||||
|
def test_template_render_with_autoescape(self):
|
||||||
|
"""
|
||||||
|
Test that HTML is correctly escaped in Browsable API views.
|
||||||
|
"""
|
||||||
|
template = Template("{% load rest_framework %}{{ content|urlize_quoted_links }}")
|
||||||
|
rendered = template.render(Context({'content': '<script>alert()</script> http://example.com'}))
|
||||||
|
assert rendered == '<script>alert()</script>' \
|
||||||
|
' <a href="http://example.com" rel="nofollow">http://example.com</a>'
|
||||||
|
|
||||||
def test_template_render_with_noautoescape(self):
|
def test_template_render_with_noautoescape(self):
|
||||||
"""
|
"""
|
||||||
Test if the autoescape value is getting passed to urlize_quoted_links filter.
|
Test if the autoescape value is getting passed to urlize_quoted_links filter.
|
||||||
|
@ -312,8 +321,8 @@ class URLizerTests(TestCase):
|
||||||
template = Template("{% load rest_framework %}"
|
template = Template("{% load rest_framework %}"
|
||||||
"{% autoescape off %}{{ content|urlize_quoted_links }}"
|
"{% autoescape off %}{{ content|urlize_quoted_links }}"
|
||||||
"{% endautoescape %}")
|
"{% endautoescape %}")
|
||||||
rendered = template.render(Context({'content': '"http://example.com"'}))
|
rendered = template.render(Context({'content': '<b> "http://example.com" </b>'}))
|
||||||
assert rendered == '"<a href="http://example.com" rel="nofollow">http://example.com</a>"'
|
assert rendered == '<b> "<a href="http://example.com" rel="nofollow">http://example.com</a>" </b>'
|
||||||
|
|
||||||
|
|
||||||
@unittest.skipUnless(coreapi, 'coreapi is not installed')
|
@unittest.skipUnless(coreapi, 'coreapi is not installed')
|
||||||
|
|
Loading…
Reference in New Issue
Block a user