@@ -108,24 +108,44 @@ your database.
108108Migrating from 2.4.0 to 2.5.0
109109=============================
110110
111- .. _CVE-2025-pending :
111+ .. _CVE-2025-53865 :
112112
113113XSS 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+
116136The devel and responsive templates prior to Roundup 2.5 used this
117137construct::
118138
119139 tal:content="structure context/MUMBLE/plain"
120140
121141Where ``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
130150This fix will apply if your tracker is based on the responsive or
131151devel template. Check the TEMPLATE-INFO.txt file in your tracker
@@ -138,53 +158,190 @@ field. For example a Name like::
138158
139159shows 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
149169prevents the attack.
150170
151171To 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
158178which 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
174196If 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
186208The 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
189346Deprecation Notices (required)
190347------------------------------
0 commit comments