Skip to content
Permalink
Browse files Browse the repository at this point in the history
reports: Escape user names in generated reports
This avoids stored XSS.

See https://hackerone.com/reports/1485226
  • Loading branch information
nijel committed Feb 20, 2022
1 parent 0df9304 commit 22d577b
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 11 deletions.
27 changes: 21 additions & 6 deletions weblate/trans/tests/test_reports.py
Expand Up @@ -31,7 +31,7 @@
"count": 1,
"count_edit": 0,
"count_new": 1,
"name": "Weblate Test",
"name": "Weblate <b>Test</b>",
"words": 2,
"words_edit": 0,
"words_new": 2,
Expand Down Expand Up @@ -62,7 +62,9 @@ class BaseReportsTest(ViewTestCase):
def setUp(self):
super().setUp()
self.user.is_superuser = True
self.user.full_name = "Weblate <b>Test</b>"
self.user.save()
self.maxDiff = None

def add_change(self):
self.edit_unit("Hello, world!\n", "Nazdar svete!\n")
Expand All @@ -87,7 +89,14 @@ def test_credits_one(self, expected_count=1):
translation__component=self.component,
)
self.assertEqual(
data, [{"Czech": [("weblate@example.org", "Weblate Test", expected_count)]}]
data,
[
{
"Czech": [
("weblate@example.org", "Weblate <b>Test</b>", expected_count)
]
}
],
)

def test_credits_more(self):
Expand Down Expand Up @@ -126,15 +135,21 @@ def test_credits_view_json(self):
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
response.content.decode(),
[{"Czech": [["weblate@example.org", "Weblate Test", 1]]}],
[{"Czech": [["weblate@example.org", "Weblate <b>Test</b>", 1]]}],
)

def test_credits_view_rst(self):
response = self.get_credits("rst")
self.assertEqual(response.status_code, 200)
self.assertEqual(
response.content.decode(),
"\n\n* Czech\n\n * Weblate Test <weblate@example.org> (1)\n\n",
"""
* Czech
* Weblate &lt;b&gt;Test&lt;/b&gt; <weblate@example.org> (1)
""",
)

def test_credits_view_html(self):
Expand All @@ -145,7 +160,7 @@ def test_credits_view_html(self):
"<table>\n"
"<tr>\n<th>Czech</th>\n"
'<td><ul><li><a href="mailto:weblate@example.org">'
"Weblate Test</a> (1)</li></ul></td>\n</tr>\n"
"Weblate &lt;b&gt;Test&lt;/b&gt;</a> (1)</li></ul></td>\n</tr>\n"
"</table>",
)

Expand Down Expand Up @@ -231,7 +246,7 @@ def test_counts_view_html(self):
<th>Target chars edited</th>
</tr>
<tr>
<td>Weblate Test</td>
<td>Weblate &lt;b&gt;Test&lt;/b&gt;</td>
<td>weblate@example.org</td>
<td>1</td>
<td>14</td>
Expand Down
13 changes: 8 additions & 5 deletions weblate/trans/views/reports.py
Expand Up @@ -17,9 +17,9 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#


from django.contrib.auth.decorators import login_required
from django.http import HttpResponse, JsonResponse
from django.utils.html import escape
from django.views.decorators.http import require_POST

from weblate.lang.models import Language
Expand Down Expand Up @@ -109,10 +109,13 @@ def get_credits(request, project=None, component=None):
for language in data:
name, translators = language.popitem()
result.append(row_start)
result.append(language_format.format(name))
result.append(language_format.format(escape(name)))
result.append(
translator_start
+ "\n".join(translator_format.format(*t) for t in translators)
+ "\n".join(
translator_format.format(escape(t[0]), escape(t[1]), t[2])
for t in translators
)
+ translator_end
)
result.append(row_end)
Expand Down Expand Up @@ -288,8 +291,8 @@ def get_counts(request, project=None, component=None):
result.append(
"".join(
(
cell_name.format(item["name"] or "Anonymous"),
cell_name.format(item["email"] or ""),
cell_name.format(escape(item["name"]) or "Anonymous"),
cell_name.format(escape(item["email"]) or ""),
cell_count.format(item["count"]),
cell_count.format(item["edits"]),
cell_count.format(item["words"]),
Expand Down

0 comments on commit 22d577b

Please sign in to comment.