Feb 24, 2026·6 min read·5 visits
CoreShop versions prior to 4.1.8 contain a Blind SQL Injection vulnerability in the Admin Reports interface. User input (e.g., store IDs, limits) is directly concatenated into raw SQL queries. Authenticated admins can exploit this to extract sensitive database information. Fixed in version 4.1.8 by implementing proper parameter binding and integer casting.
A classic Blind SQL Injection vulnerability was discovered in the administrative reporting modules of CoreShop, a popular e-commerce framework for Pimcore. Despite the availability of modern ORM features, developers resorted to manual string concatenation for SQL queries, allowing authenticated administrators to inject arbitrary SQL commands. This flaw permits data exfiltration via boolean or time-based techniques, effectively turning the reporting dashboard into a database dumping tool.
CoreShop is the heavy lifter for e-commerce on the Pimcore platform. It handles the carts, the money, and the customers. Naturally, it comes with a suite of administrative reports to tell you how many people abandoned their carts or which payment provider is making you the most money. These reports are usually the driest part of the application—grids of numbers meant for managers.
But under the hood, these reports were constructed with a recklessness that harkens back to the PHP tutorials of 2005. While the rest of the application likely leverages the safety of Symfony's Doctrine ORM, the reporting module decided to go 'off-roading' with raw SQL. The result? A direct pipeline from a URL parameter to the database engine.
This vulnerability is a perfect example of the 'secure framework fallacy'—the belief that using a modern, secure framework like Symfony automatically protects you. It doesn't help if you deliberately bypass its safety features to concatenate strings like a cowboy.
The root cause here is technically simple but conceptually frustrating. The vulnerability exists within src/CoreShop/Bundle/CoreBundle/Report/, affecting nearly every report class from AbandonedCartsReport.php to VouchersReport.php. The application accepts parameters from the request—things like store, offset, limit, and orderState—and passes them into a function that builds a SQL query string.
Instead of using parameterized queries (prepared statements) for every variable, the code mixed and matched. It used ? placeholders for dates, but for the store ID or the LIMIT clause, it just dropped the variable right into the string. It looked something like this:
$sqlQuery = "SELECT ... WHERE cart.store = $storeId ... LIMIT $offset,$limit";This is the coding equivalent of locking your front door (using authentication) but leaving the window wide open. Because the $storeId variable wasn't sanitized or typed, an attacker could append SQL logic. Since the interface doesn't dump stack traces to the screen (blind), you can't just UNION SELECT your way to glory immediately—you have to ask the database Yes/No questions or tell it to go to sleep.
Let's look at the diff, because nothing explains bad code like seeing it fixed. The vulnerability lived in how the _sql string was constructed in the getData method of the report classes.
Before (Vulnerable):
The code directly interpolated variables. Note the $store and $limit variables sitting naked in the query string.
$sql = 'SELECT ' . $query . '
FROM ' . $this->getTableName() . ' cart
WHERE cart.store = ' . $params['store'] . '
LIMIT ' . $params['offset'] . ',' . $params['limit'];
$stmt = $this->connection->executeQuery($sql);After (Fixed):
The patch introduces named placeholders (:store) and enforces strict type casting for syntax elements that are hard to parameterize, like LIMIT and OFFSET.
$sql = 'SELECT ' . $query . '
FROM ' . $this->getTableName() . ' cart
WHERE cart.store = :store
LIMIT ' . (int)$params['offset'] . ',' . (int)$params['limit'];
$stmt = $this->connection->executeQuery($sql, ['store' => $params['store']]);> [!NOTE]
> The patch also includes a clever fix for IN clauses (arrays), dynamically generating placeholders like :id0, :id1 instead of imploding the array into the string. This closes the door on array-based injection vectors as well.
So, you are an authenticated administrator. Maybe you are a low-level admin with access to reports but not user management, and you want to escalate. Or maybe you've hijacked a session. You browse to the 'Abandoned Carts' report. The URL looks like this:
https://admin.target.com/admin/reports/abandoned-carts?store=1
The exploit involves modifying that store parameter. Since we are in a Blind SQLi scenario, we can't see the output of our injection, but we can infer it.
Scenario 1: Boolean Blind We inject a condition that is true, then one that is false, and watch if the report returns data.
?store=1 AND 1=1 -> Report loads normally.?store=1 AND 1=0 -> Report is empty.If the server behaves differently, we can script this to extract data bit by bit.
Scenario 2: Time-Based If the report always returns empty or behaves strangely, we use time. We tell the database to sleep if our guess is correct.
?store=1 AND SLEEP(5)If the report takes 5+ seconds to load, we know we have execution. We can then ask: "Is the first letter of the admin password 'A'? If yes, sleep for 5 seconds."
The CVSS score is a modest 4.9, mostly because it requires High Privileges (PR:H). But don't let that fool you. In the context of e-commerce, 'High Privilege' accounts are often shared or less protected than root infrastructure accounts.
Successful exploitation allows an attacker to dump the entire database. This includes:
While the attacker cannot modify data (assuming the DB user is read-only for reports, which is a best practice often ignored), the confidentiality loss is total. In a GDPR/CCPA world, this is a nightmare scenario.
The remediation is straightforward: Upgrade to CoreShop 4.1.8. The patch replaces the concatenation with Doctrine's parameter binding mechanisms.
If you cannot upgrade immediately, you are in a tight spot because this logic is baked into the core classes. You could try to mitigate this via a Web Application Firewall (WAF) by blocking common SQL keywords (UNION, SELECT, SLEEP, BENCHMARK) in query parameters, specifically for the /admin/reports path. However, WAFs are bypassable. The only real fix is code that doesn't treat user input like a trusted friend.
Key Takeaway for Developers:
Never, ever trust ParameterBag values inside a raw SQL string. Even if you think "It's just an integer," cast it explicitly (int)$val. Better yet, use the Query Builder or Prepared Statements for everything. No exceptions.
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
CoreShop CoreShop | < 4.1.8 | 4.1.8 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-89 (SQL Injection) |
| CVSS v3.1 | 4.9 (Medium) |
| Attack Vector | Network (Authenticated) |
| Privileges Required | High (Admin) |
| Impact | High Confidentiality Loss |
| EPSS Score | 0.00012 |
| Exploit Status | PoC Available |
Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')