VYPR
Moderate severityNVD Advisory· Published Feb 11, 2026· Updated Mar 5, 2026

Kimai 2- persistent cross-site scripting (XSS)

CVE-2019-25317

Description

Kimai 2 contains a persistent cross-site scripting vulnerability that allows attackers to inject malicious scripts into timesheet descriptions. Attackers can insert SVG-based XSS payloads in the description field to execute arbitrary JavaScript when the page is loaded and viewed by other users.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Kimai 2 contains a stored XSS vulnerability in timesheet descriptions allowing attackers to inject SVG-based payloads for arbitrary JavaScript execution.

CVE-2019-25317 describes a persistent cross-site scripting (XSS) vulnerability in Kimai 2, an open-source time-tracking application. The flaw exists in the timesheet description field, where users can input SVG-based XSS payloads that are not properly sanitized. When the timesheet page is loaded, the malicious script executes in the browser of any user viewing the description [1].

An attacker must have the ability to create or edit timesheet entries, which typically requires authenticated access to the application. The attack is low-complexity: the attacker simply inserts a crafted SVG element containing JavaScript code into the description field. No special privileges beyond normal user permissions are needed, and the payload persists in the database, affecting all users who view the affected timesheet [1].

Successful exploitation allows the attacker to execute arbitrary JavaScript in the context of the victim's session. This can lead to theft of sensitive data, session hijacking, defacement of the application interface, or forced actions on behalf of the victim. Because the script runs in the user's browser, it can access cookies, local storage, and perform actions with the victim's privileges [1].

The vulnerability has been addressed in a commit that applies proper escaping to the description output using the escape Twig filter before passing it through the desc2html filter [2]. Users should update to a version that includes this fix. No workarounds are documented, but upgrading to the latest Kimai 2 release is recommended.

AI Insight generated on May 19, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
kimai/kimaiPackagist
< 1.11.1

Affected products

1
  • kevinpapst/Kimaiv5
    Range: 2

Patches

1
a0e8aa3a4357

fixed xss in timesheet description (#962)

https://github.com/kimai/kimaiKevin PapstJul 14, 2019via ghsa
7 files changed · +7 7
  • src/Twig/MarkdownExtension.php+1 1 modified
    @@ -77,6 +77,6 @@ public function timesheetContent($content): string
          */
         public function markdownToHtml(string $content): string
         {
    -        return $this->markdown->toHtml($content, true);
    +        return $this->markdown->toHtml($content, false);
         }
     }
    
  • templates/export/renderer/default.html.twig+1 1 modified
    @@ -276,7 +276,7 @@
                             </td>
                             <td class="column-description" {% if not columns.description %}style="display: none"{% endif %}>
                                 {% if entry.description is not empty %}
    -                                {{ entry.description|desc2html }}
    +                                {{ entry.description|escape|desc2html }}
                                 {% endif %}
                             </td>
                             <td class="column-exported" {% if not columns.exported %}style="display: none"{% endif %}>
    
  • templates/export/renderer/pdf.html.twig+1 1 modified
    @@ -151,7 +151,7 @@ mpdf-->
                     {{ entry.project.customer.name }} - {{ entry.project.name }} - {{ entry.activity.name }}
                     {% if entry.description is not empty %}
                         <br>
    -                    <i>{{ entry.description|desc2html }}</i>
    +                    <i>{{ entry.description|escape|desc2html }}</i>
                     {% endif %}
                 </td>
                 <td class="duration">{{ entry.duration|duration }}</td>
    
  • templates/invoice/index.html.twig+1 1 modified
    @@ -56,7 +56,7 @@
                     <td class="{{ tables.data_table_column_class(tableName, columns, 'user') }}">{{ widgets.label_user(entry.user) }}</td>
                     <td class="{{ tables.data_table_column_class(tableName, columns, 'description') }} timesheet-description">
                         {% if entry.description is not empty %}
    -                        {{ entry.description|desc2html }}
    +                        {{ entry.description|escape|desc2html }}
                         {% else %}
                             {{ entry.activity.name }} / {{ entry.project.name }}
                         {% endif %}
    
  • templates/timesheet/export.html.twig+1 1 modified
    @@ -41,7 +41,7 @@
                             <td>
                                 {% if entry.description is not empty %}
                                 <div>
    -                                {{ entry.description|desc2html }}
    +                                {{ entry.description|escape|desc2html }}
                                 </div>
                                 {% endif %}
                                 <span class="small">
    
  • templates/timesheet/index.html.twig+1 1 modified
    @@ -95,7 +95,7 @@
                     <td class="{{ tables.data_table_column_class(tableName, columns, 'customer') }}">{{ widgets.label_customer(entry.project.customer) }}</td>
                     <td class="{{ tables.data_table_column_class(tableName, columns, 'project') }}">{{ widgets.label_project(entry.project) }}</td>
                     <td class="{{ tables.data_table_column_class(tableName, columns, 'activity') }}">{{ widgets.label_activity(entry.activity) }}</td>
    -                <td class="{{ tables.data_table_column_class(tableName, columns, 'description') }} timesheet-description">{{ entry.description|desc2html }}</td>
    +                <td class="{{ tables.data_table_column_class(tableName, columns, 'description') }} timesheet-description">{{ entry.description|escape|desc2html }}</td>
                     <td class="{{ tables.data_table_column_class(tableName, columns, 'tags') }}">{{ widgets.tag_list(entry.tags) }}</td>
                     <td class="actions">
                         {{- actions.timesheet(entry, 'index') -}}
    
  • templates/timesheet-team/export.html.twig+1 1 modified
    @@ -49,7 +49,7 @@
                             <td>
                                 {% if entry.description is not empty %}
                                 <div>
    -                                {{ entry.description|desc2html }}
    +                                {{ entry.description|escape|desc2html }}
                                 </div>
                                 {% endif %}
                                 <span class="small">
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.