From 022ca8ad6cf3a8a2f114c0e4f7bd83ea9edb044d Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Sun, 20 Aug 2017 15:30:13 +0200 Subject: [PATCH] [SECURITY] Fix XSS injection vulnerabilities in question answers, event, quota and product names --- src/pretix/base/models/log.py | 13 +++++++------ src/pretix/base/templatetags/escapejson.py | 13 +++++++++++++ .../templates/pretixcontrol/items/question.html | 3 ++- .../templates/pretixcontrol/items/quota.html | 3 ++- src/pretix/control/views/dashboards.py | 6 ++++-- src/pretix/helpers/escapejson.py | 16 ++++++++++++++++ .../pretixplugins/statistics/index.html | 7 ++++--- 7 files changed, 48 insertions(+), 13 deletions(-) create mode 100644 src/pretix/base/templatetags/escapejson.py create mode 100644 src/pretix/helpers/escapejson.py diff --git a/src/pretix/base/models/log.py b/src/pretix/base/models/log.py index c01ef64c66..34f7aa9ec3 100644 --- a/src/pretix/base/models/log.py +++ b/src/pretix/base/models/log.py @@ -5,6 +5,7 @@ from django.contrib.contenttypes.models import ContentType from django.db import models from django.urls import reverse from django.utils.functional import cached_property +from django.utils.html import escape from django.utils.translation import ugettext_lazy as _ @@ -66,7 +67,7 @@ class LogEntry(models.Model): 'organizer': self.event.organizer.slug, 'code': co.code }), - 'val': co.code, + 'val': escape(co.code), } elif isinstance(co, Voucher): a_text = _('Voucher {val}…') @@ -76,7 +77,7 @@ class LogEntry(models.Model): 'organizer': self.event.organizer.slug, 'voucher': co.id }), - 'val': co.code[:6], + 'val': escape(co.code[:6]), } elif isinstance(co, Item): a_text = _('Product {val}') @@ -86,7 +87,7 @@ class LogEntry(models.Model): 'organizer': self.event.organizer.slug, 'item': co.id }), - 'val': co.name, + 'val': escape(co.name), } elif isinstance(co, Quota): a_text = _('Quota {val}') @@ -96,7 +97,7 @@ class LogEntry(models.Model): 'organizer': self.event.organizer.slug, 'quota': co.id }), - 'val': co.name, + 'val': escape(co.name), } elif isinstance(co, ItemCategory): a_text = _('Category {val}') @@ -106,7 +107,7 @@ class LogEntry(models.Model): 'organizer': self.event.organizer.slug, 'category': co.id }), - 'val': co.name, + 'val': escape(co.name), } elif isinstance(co, Question): a_text = _('Question {val}') @@ -116,7 +117,7 @@ class LogEntry(models.Model): 'organizer': self.event.organizer.slug, 'question': co.id }), - 'val': co.question, + 'val': escape(co.question), } if a_text and a_map: diff --git a/src/pretix/base/templatetags/escapejson.py b/src/pretix/base/templatetags/escapejson.py new file mode 100644 index 0000000000..832cc711e2 --- /dev/null +++ b/src/pretix/base/templatetags/escapejson.py @@ -0,0 +1,13 @@ +from django import template +from django.template.defaultfilters import stringfilter + +from pretix.helpers.escapejson import escapejson + +register = template.Library() + + +@register.filter("escapejson") +@stringfilter +def escapejs_filter(value): + """Hex encodes characters for use in a application/json type script.""" + return escapejson(value) diff --git a/src/pretix/control/templates/pretixcontrol/items/question.html b/src/pretix/control/templates/pretixcontrol/items/question.html index 01a1621339..11fd3a17af 100644 --- a/src/pretix/control/templates/pretixcontrol/items/question.html +++ b/src/pretix/control/templates/pretixcontrol/items/question.html @@ -1,6 +1,7 @@ {% extends "pretixcontrol/items/base.html" %} {% load i18n %} {% load bootstrap3 %} +{% load escapejson %} {% load formset_tags %} {% block title %}{% blocktrans with name=question.question %}Question: {{ name }}{% endblocktrans %}{% endblock %} {% block inside %} @@ -58,7 +59,7 @@
- +
diff --git a/src/pretix/control/templates/pretixcontrol/items/quota.html b/src/pretix/control/templates/pretixcontrol/items/quota.html index 6c876b77fd..884ef04124 100644 --- a/src/pretix/control/templates/pretixcontrol/items/quota.html +++ b/src/pretix/control/templates/pretixcontrol/items/quota.html @@ -1,6 +1,7 @@ {% extends "pretixcontrol/items/base.html" %} {% load i18n %} {% load bootstrap3 %} +{% load escapejson %} {% load eventsignal %} {% block title %}{% blocktrans with name=quota.name %}Quota: {{ name }}{% endblocktrans %}{% endblock %} {% block inside %} @@ -20,7 +21,7 @@
- +
{% trans "Availability calculation" %} diff --git a/src/pretix/control/views/dashboards.py b/src/pretix/control/views/dashboards.py index 7d9be19b1f..2d9c70ef70 100644 --- a/src/pretix/control/views/dashboards.py +++ b/src/pretix/control/views/dashboards.py @@ -8,6 +8,7 @@ from django.shortcuts import render from django.template.loader import get_template from django.utils import formats from django.utils.formats import date_format +from django.utils.html import escape from django.utils.translation import ugettext_lazy as _ from pretix.base.models import ( @@ -135,7 +136,7 @@ def quota_widgets(sender, **kwargs): status, left = q.availability() widgets.append({ 'content': NUM_WIDGET.format(num='{}/{}'.format(left, q.size) if q.size is not None else '\u221e', - text=_('{quota} left').format(quota=q.name)), + text=_('{quota} left').format(quota=escape(q.name))), 'display_size': 'small', 'priority': 50, 'url': reverse('control:event.items.quotas.show', kwargs={ @@ -256,7 +257,8 @@ def user_event_widgets(**kwargs): for event in events: widgets.append({ 'content': '
{event}{df}{dt}
'.format( - event=event.name, df=date_format(event.date_from, 'SHORT_DATE_FORMAT') if event.date_from else '', + event=escape(event.name), + df=date_format(event.date_from, 'SHORT_DATE_FORMAT') if event.date_from else '', dt=date_format(event.date_to, 'SHORT_DATE_FORMAT') if event.date_to else '' ), 'display_size': 'small', diff --git a/src/pretix/helpers/escapejson.py b/src/pretix/helpers/escapejson.py new file mode 100644 index 0000000000..6628e54bef --- /dev/null +++ b/src/pretix/helpers/escapejson.py @@ -0,0 +1,16 @@ +from django.utils import six +from django.utils.encoding import force_text +from django.utils.functional import keep_lazy +from django.utils.safestring import SafeText, mark_safe + +_json_escapes = { + ord('>'): '\\u003E', + ord('<'): '\\u003C', + ord('&'): '\\u0026', +} + + +@keep_lazy(six.text_type, SafeText) +def escapejson(value): + """Hex encodes characters for use in a application/json type script.""" + return mark_safe(force_text(value).translate(_json_escapes)) diff --git a/src/pretix/plugins/statistics/templates/pretixplugins/statistics/index.html b/src/pretix/plugins/statistics/templates/pretixplugins/statistics/index.html index 24f476118a..a87eb49e4a 100644 --- a/src/pretix/plugins/statistics/templates/pretixplugins/statistics/index.html +++ b/src/pretix/plugins/statistics/templates/pretixplugins/statistics/index.html @@ -2,6 +2,7 @@ {% load i18n %} {% load compress %} {% load staticfiles %} +{% load escapejson %} {% block title %}{% trans "Statistics" %}{% endblock %} {% block content %}

{% trans "Statistics" %}

@@ -30,9 +31,9 @@
- - - + + + {% else %}