Skip to content

Commit 65ac8f4

Browse files
committed
bug(security): CVE-2025-53865 - XSS bug
Extensive fixes in devel, responsive templates known to be exploitable. Similar constructs in classic and minimal templates not known to be exploitable, but changed anyway. doc/upgrading.txt: Reformat to 66 characters. Update with assigned CVE number. Add section on fixing tal:replace with unsafe data. Document analysis and assumptions in comment in file. doc/security.txt: Update with CVE number.
1 parent e9a6488 commit 65ac8f4

25 files changed

+228
-70
lines changed

doc/security.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ source release tarballs.
2828
CVE Announcements
2929
-----------------
3030

31-
* `CVE-2025-pending`_ - :ref:`XSS security issue with devel or
32-
responsive templates <CVE-2025-pending>`. Fixed in release 2.5.0,
31+
* `CVE-2025-53865`_ - :ref:`XSS security issue with devel or
32+
responsive templates <CVE-2025-53865>`. Fixed in release 2.5.0,
3333
directions available for fixing trackers based on these templates.
3434

3535
* `CVE-2024-39124`_ - :ref:`classhelpers (_generic.help.html) are
@@ -43,8 +43,8 @@ CVE Announcements
4343
executed. <CVE-2024-39126>` Fixed in release 2.4.0, directions
4444
available for fixing in prior versions.
4545

46-
.. _CVE-2025-pending:
47-
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2025-pending
46+
.. _CVE-2025-53865:
47+
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2025-53865
4848
.. _CVE-2024-39124:
4949
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-39124
5050
.. _CVE-2024-39125:

doc/upgrading.txt

Lines changed: 183 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -108,24 +108,44 @@ your database.
108108
Migrating from 2.4.0 to 2.5.0
109109
=============================
110110

111-
.. _CVE-2025-pending:
111+
.. _CVE-2025-53865:
112112

113113
XSS security issue with devel and responsive templates (recommended)
114114
--------------------------------------------------------------------
115115

116+
There are actually two different issues under this heading.
117+
118+
1. incorrect use of the ``structure`` keyword with
119+
``tal:content``
120+
2. use of ``tal:replace`` on unsafe input
121+
122+
In the discussion below, the :term:`html directory` means one or
123+
more directories listed in the ``templates`` key of your
124+
tracker's ``config.ini`` file.
125+
126+
These directions can be used to solve the XSS security issue with
127+
any version of Roundup. Even if you used a classic or minimal
128+
template, you should check your trackers for these issues. The
129+
classic template fixed most of these many years ago, but the
130+
updates were not made to the devel and responsive templates. No
131+
report of similar issues with the jinja template has been seen.
132+
133+
Incorrect use of structure in templates
134+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
135+
116136
The devel and responsive templates prior to Roundup 2.5 used this
117137
construct::
118138

119139
tal:content="structure context/MUMBLE/plain"
120140

121141
Where ``MUMBLE`` is a property of your issues (e.g. title).
122142

123-
This construct allows a URL with a carefully crafted query parameter
124-
to execute arbitrary JavaScript.
143+
This construct allows a URL with a carefully crafted query
144+
parameter to execute arbitrary JavaScript.
125145

126-
You should check all your trackers. The classic template has not used
127-
this construct since at least 2009, but your tracker's templates may
128-
use the offending construct anyway.
146+
You should check all your trackers. The classic template has not
147+
used this construct since at least 2009, but your tracker's
148+
templates may use the offending construct anyway.
129149

130150
This fix will apply if your tracker is based on the responsive or
131151
devel template. Check the TEMPLATE-INFO.txt file in your tracker
@@ -138,53 +158,190 @@ field. For example a Name like::
138158

139159
shows that tracker is based on the responsive or devel templates.
140160

141-
.. _cve-2025-pending-fixed:
161+
.. _cve-2025-53865-fixed:
142162

143-
To fix this, remove the ``structure`` declaration when it is used with
144-
a plain representation. So fixing the code by replacing the example
145-
above with::
163+
To fix this, remove the ``structure`` declaration when it is used
164+
with a plain representation. So fixing the code by replacing the
165+
example above with::
146166

147167
tal:content="context/MUMBLE/plain"
148168

149169
prevents the attack.
150170

151171
To check for this issue, search for ``structure`` followed by
152172
``/plain`` in all your html templates. If you are on a Linux/Unix
153-
system you can search the html subdirectory of your tracker with the
154-
following::
173+
system you can search the html subdirectory of your tracker with
174+
the following::
155175

156176
grep 'structure.*/plain' *.html
157177

158178
which should return any lines with issues.
159179

160180
.. warning::
161181

162-
Backup the files in the ``html`` subdirectory of your tracker in case
163-
an edit goes wrong.
182+
Backup the files in the ``html`` subdirectory of your tracker
183+
in case an edit goes wrong.
164184

165-
You can fix this issue using the GNU sed command::
185+
As an example, you could fix this issue using the GNU sed
186+
command::
166187

167188
sed -i.bak -e '/structure.*\/plain/s/structure.//' *.html
168189

169-
to edit the files in place and remove the structure keyword. It will
170-
create a ``.bak`` file with the original contents of the file. If you
171-
are on windows, some text editors support search and replace using a
172-
regular expression.
190+
to edit the files in place and remove the structure keyword. It
191+
will create a ``.bak`` file with the original contents of the
192+
file. If your templates were changed, this might still miss some
193+
entries. If you are on windows, some text editors support search
194+
and replace using a regular expression.
173195

174196
If the construct is split across lines::
175197

176198
tal:content="structure
177199
context/MUMBLE/plain"
178200

179-
the commands above will miss the construct. So you should also search
180-
the html files using ``grep /plain *.html`` and verify that all of the
181-
``context/MUMBLE/plain`` include ``tal:content`` as in the `fixed
182-
example above <#cve-2025-pending-fixed>`_. Any lines that have
183-
``context/MUMBLE/plain`` without ``tal:content=`` before it need to be
184-
manually verified/fixed.
201+
the commands above will miss the construct. So you should also
202+
search the html files using ``grep /plain *.html`` and verify
203+
that all of the ``context/MUMBLE/plain`` include ``tal:content``
204+
as in the `fixed example above <#cve-2025-53865-fixed>`_. Any
205+
lines that have ``context/MUMBLE/plain`` without ``tal:content=``
206+
before it need to be manually verified/fixed.
185207

186208
The distributed devel and responsive templates do not split the
187-
construct across lines, but if you changed the files it may be split.
209+
construct across lines, but if you changed the files it may be
210+
split.
211+
212+
tal:replace used with unsafe input
213+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
214+
215+
The problem was caused by the following markup::
216+
217+
<span tal:replace="context/MUMBLE" />
218+
219+
in the head of the ``bug.item.html``, ``task.item.html`` and
220+
other files in the devel and responsive templates.
221+
222+
This was fixed many years ago in the classic template's
223+
``index.item.html``. The classic template replaces the above
224+
construct with::
225+
226+
<tal:x tal:content="context/MUMBLE" />
227+
228+
``tal:content`` explicitly escapes the result unless the
229+
``structure`` directive is used. ``tal:replace`` expects the
230+
result to be safe and usable in an HTML context.
231+
232+
TAL drops any tags that it doesn't know about from the output.
233+
``<tal:x tal:content="..." />`` results in the value of the
234+
content expression without a surrounding html tag. (Effectively
235+
replacing the construct.)
236+
237+
The following diff for ``bug.item.html`` in the devel template
238+
shows the change to make things safe (remove lines starting with
239+
``-`` and add lines staring with ``+``)::
240+
241+
<tal:block metal:use-macro="templates/page/macros/frame">
242+
<title metal:fill-slot="head_title">
243+
<tal:block condition="context/id" i18n:translate=""
244+
- >Bug <span tal:replace="context/id" i18n:name="id"
245+
- />: <span tal:replace="context/title" i18n:name="title"
246+
- /> - <span tal:replace="config/TRACKER_NAME" i18n:name="tracker"
247+
+ >Bug <tal:x tal:content="context/id" i18n:name="id"
248+
+ />: <tal:x tal:content="context/title" i18n:name="title"
249+
+ /> - <tal:x tal:content="config/TRACKER_NAME" i18n:name="tracker"
250+
/></tal:block>
251+
<tal:block condition="not:context/id" i18n:translate=""
252+
>New Bug report - <span tal:replace="config/TRACKER_NAME" i18n:name="tracker"
253+
254+
A similar change was applied in the following html files in the
255+
devel or responsive templates:
256+
257+
.. rst-class:: multicol
258+
259+
* _generic.collision.html
260+
* bug.item.html
261+
* keyword.item.html
262+
* milestone.item.html
263+
* msg.item.html
264+
* task.item.html
265+
* user.item.html
266+
267+
Also ``page.html`` should be changed from::
268+
269+
<p class="label"><b tal:replace="request/user/username">username</b></p>
270+
271+
to::
272+
273+
<p class="label"><b tal:replace="python:request.user.username.plain(escape=1)">username</b></p>
274+
275+
The code audit found the ``tal:replace`` construct is used with
276+
``context/id`` and ``context/designator`` paths. The references
277+
to these paths have been changed to use ``tal:x`` in the classic
278+
template's ``msg.item.html`` file and the classic and minimal
279+
template's ``_generic.collision.html`` file.
280+
281+
These paths are critical to navigation in Roundup and are set
282+
from the path part of the URL. Roundup's URL path validation
283+
makes it unlikely that an attacker could exploit them. If you
284+
wish you can change your templates or copy the corresponding
285+
files from the template if you haven't made local changes.
286+
287+
Also you may have used copies of these insecure templates
288+
elsewhere in your tracker (e.g. to create a feature class). To
289+
find other possible issues you can use the command::
290+
291+
grep -r "tal:replace=" *.html
292+
293+
in your tracker's :term:`html directory`. Check each occurrence
294+
and if needed, change it to the safer form. You should consider
295+
any reference to ``context`` to be under the user's (attacker's)
296+
control. Also ``db`` (excluding ``db/config``) and ``request``
297+
references that use user supplied content
298+
(e.g. ``request/user/username`` above) should be changed to
299+
``tal:x`` form
300+
301+
.. comment:
302+
As part of the analysis, the following command was used to find
303+
potentially vulnerable stuff in the templates. Each grep -v was
304+
removed to display items in that category and they were checked::
305+
306+
grep -r 'tal:replace' . | grep -v 'replace="batch' | \
307+
grep -v 'replace="config' | grep -v 'replace="db/config' | \
308+
grep -v 'replace="structure' | grep -v 'replace="python:' | \
309+
grep -v 'replace="request/'
310+
311+
312+
context/id, context/designator:
313+
assume safe if used in an class.item.html page as the page
314+
wouldn't be shown if they weren't valid numbers/designators.
315+
316+
Might not be ok referenced in a _generic fallback page though.
317+
318+
config, db/config, batch, nothing:
319+
should be safe as they are not under user control
320+
321+
request/classname (python:request._classname), request/template:
322+
should be safe as they are needed to navigate to a display page,
323+
so if they are invalid nothing will be displayed.
324+
325+
utils, python:
326+
assume it's written correctly and is safe (could use some new
327+
tests for the shipped utility functions). The intent of these
328+
can be to deliver blocks of <script> or other html markup.
329+
330+
db, request:
331+
might be dangerous when accessing user supplied values.
332+
333+
request/user/username:
334+
Escape these. If the username is an XSS issue, an attacker could
335+
use it to compromise a user.
336+
337+
request/dispname:
338+
should be quoted and is by the existing python: code.
339+
340+
Open question: why does there have to be an error generated by the
341+
url @sort=1. Without invalid sort param, the exploit url doesn't
342+
work and the context appears to use the database's title not the one
343+
in the url. Also its not positional @sort=1 can appear anywhere in
344+
the url.
188345

189346
Deprecation Notices (required)
190347
------------------------------

share/roundup/templates/classic/html/_generic.collision.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@
1111
There has been a collision. Another user updated this node
1212
while you were editing. Please <a href='${context}'>reload</a>
1313
the node and review your edits.
14-
"><span tal:replace="context/designator" i18n:name="context" />
14+
"><tal:x tal:content="context/designator" i18n:name="context" />
1515
</td>
1616
</tal:block>

share/roundup/templates/classic/html/msg.item.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<tal:block metal:use-macro="templates/page/macros/icing">
33
<title metal:fill-slot="head_title">
44
<tal:block condition="context/id" i18n:translate=""
5-
>Message <span tal:replace="context/id" i18n:name="id"
5+
>Message <tal:x tal:content="context/id" i18n:name="id"
66
/> - <span tal:replace="config/TRACKER_NAME" i18n:name="tracker"
77
/></tal:block>
88
<tal:block condition="not:context/id" i18n:translate=""

share/roundup/templates/devel/html/_generic.collision.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@
1111
There has been a collision. Another user updated this node
1212
while you were editing. Please <a href='${context}'>reload</a>
1313
the node and review your edits.
14-
"><span tal:replace="context/designator" i18n:name="context" />
14+
"><tal:x tal:content="context/designator" i18n:name="context" />
1515
</td>
1616
</tal:block>

share/roundup/templates/devel/html/bug.item.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
<tal:block metal:use-macro="templates/page/macros/frame">
22
<title metal:fill-slot="head_title">
33
<tal:block condition="context/id" i18n:translate=""
4-
>Bug <span tal:replace="context/id" i18n:name="id"
5-
/>: <span tal:replace="context/title" i18n:name="title"
6-
/> - <span tal:replace="config/TRACKER_NAME" i18n:name="tracker"
4+
>Bug <tal:x tal:content="context/id" i18n:name="id"
5+
/>: <tal:x tal:content="context/title" i18n:name="title"
6+
/> - <tal:x tal:content="config/TRACKER_NAME" i18n:name="tracker"
77
/></tal:block>
88
<tal:block condition="not:context/id" i18n:translate=""
99
>New Bug report - <span tal:replace="config/TRACKER_NAME" i18n:name="tracker"

share/roundup/templates/devel/html/keyword.item.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
>
44
<title metal:fill-slot="head_title">
55
<tal:if condition="context/id" i18n:translate=""
6-
>Keyword <span tal:replace="context/id" i18n:name="id"
7-
/>: <span tal:replace="context/name" i18n:name="title"
6+
>Keyword <tal:x tal:content="context/id" i18n:name="id"
7+
/>: <tal:x tal:content="context/name" i18n:name="title"
88
/> - <span tal:replace="config/TRACKER_NAME" i18n:name="tracker"
99
/></tal:if>
1010
<tal:if condition="not:context/id" i18n:translate=""

share/roundup/templates/devel/html/milestone.item.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<tal:block metal:use-macro="templates/page/macros/frame">
44
<tal:block metal:fill-slot="body_title">
5-
<p class="header">Milestone<span tal:replace="context/id" /> Editing</p>
5+
<p class="header">Milestone<tal:x tal:content="context/id" /> Editing</p>
66
</tal:block>
77

88
<tal:block metal:fill-slot="content">

share/roundup/templates/devel/html/msg.item.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<tal:block metal:use-macro="templates/page/macros/frame">
22
<title metal:fill-slot="head_title">
33
<tal:block condition="context/id" i18n:translate=""
4-
>Message <span tal:replace="context/id" i18n:name="id"
4+
>Message <tal:x tal:content="context/id" i18n:name="id"
55
/> - <span tal:replace="config/TRACKER_NAME" i18n:name="tracker"
66
/></tal:block>
77
<tal:block condition="not:context/id" i18n:translate=""

share/roundup/templates/devel/html/page.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ <h1><a href="/">Roundup Demo Tracker</a></h1>
190190
</form>
191191
</li>
192192
<li tal:condition="python:request.user.username != 'anonymous'" class="submenu">
193-
<p class="label"><b tal:replace="request/user/username">username</b></p>
193+
<p class="label"><b tal:replace="python:request.user.username.plain(escape=1)">username</b></p>
194194
<ul>
195195
<li>
196196
<a href="#" tal:attributes="href python:request.indexargs_url('bug', {

0 commit comments

Comments
 (0)