Skip to content
MAGEKWIKScanner
critical CVE-2019-7139 CVSS 9.8

CVE-2019-7139: the Ambionics pre-auth SQL injection in Magento 2

Magekwik Security 6 min read

CRITICAL CVES CVE-2019-7139 CVSS 9.8 scan.magekwik.com

Charles Fol's PRODSECBUG-2198 turned a storefront product-tracking endpoint into unauthenticated SQL injection via a placeholder-reuse bug in prepareSqlCondition() — and a read-only leak into admin takeover. Root cause, exploitation, and the NVD-authoritative patch path. (The RCE that completed the chain was a separate CVE.)

What it is

CVE-2019-7139 (Magento tracker PRODSECBUG-2198) is a pre-authentication SQL injection reachable through the storefront catalog/product_frontend_action/synchronize controller. It was reported by Charles Fol of Ambionics Security and patched in the March 2019 quarterly release. No account, no CSRF token, and no admin surface is required: an anonymous request to a public product-view-tracking endpoint yields arbitrary read access to the database, and from there a practical path to full admin takeover and code execution.

Scope

Affected (per NVD): Magento 2.1 ≤ 2.1.17, 2.2 ≤ 2.2.8, 2.3 ≤ 2.3.1. Fixed in 2.1.18 / 2.2.9 / 2.3.2. NVD scores this CVSS 9.8 (CWE-89). Note the source conflict: Magento's March 2019 advisory patched PRODSECBUG-2198 in 2.3.1 / 2.2.8 / 2.1.17, but NVD still lists 2.3.1 as affected for this CVE id — so treat 2.1.18 / 2.2.9 / 2.3.2 as the safe floor.

Root cause

The bug is not in the controller but in the query builder shared by the whole framework: Magento\Framework\DB\Adapter\Pdo\Mysql::prepareSqlCondition(). When a condition specifies a bounded range using both from and to keys, the method builds the SQL in two steps. The second step re-quotes the already-partially-built fragment instead of quoting only the new value:

The defective placeholder handling (simplified)
// $query already contains the rendered `from` fragment
$query = $this->_prepareQuotedSqlCondition(
    $query . $conditionKeyMap['to'], $to, $fieldName
);
// every remaining `?` is replaced — including ones the attacker
// smuggled in through the `from` value

Placeholder substitution is positional and global over the string. Because the attacker controls the from value, planting a ? there causes the to value to be substituted at that position instead of at the intended bind point. The to value is concatenated without quoting, so the second half of the range condition is injected verbatim into the WHERE clause. The prepared-statement guarantee is silently broken by the framework's own condition assembler.

How attackers abuse it

The Synchronize action deserialises a JSON body of "frontend actions" (recently viewed / compared products) and passes the ids map straight into a collection filter, which lands in prepareSqlCondition(). A crafted range condition breaks out of the intended predicate:

POST body shape that reaches the injection
type_id=recently_products
ids[0][added_at]=...
ids[0][product_id][from]=?
ids[0][product_id][to]=))) OR (SELECT ... UNION SELECT ...) -- -

The endpoint returns no query results to the client, so exploitation is blind — boolean- and time-based extraction via sqlmap or a bespoke oracle. Read access alone is enough to pivot: dump admin_user password hashes for offline cracking, or read admin_user_session / core_config_data to obtain the crypt key, forge or hijack an admin session, and recover a valid form_key. Fol later disclosed a separate remote-code-execution vulnerability — a distinct CVE, not CVE-2019-7139 itself — that uses an authenticated primitive (template/layout handling and PHAR-style object deserialization) to turn admin access into php on the box. Chained, the two bugs walked a store from an unauthenticated storefront request to a shell; but keep the CVE ids distinct — this post is about the SQL injection.

Who got hit / real examples

This did not stay theoretical for long. Within days of the 2.3.1 release, working proof-of-concept code and sqlmap tamper recipes were public (Vulhub shipped a reproducible 2.2 lab), which dropped the exploitation bar to commodity scanning. Magento's own advisory urged emergency patching precisely because the injection is unauthenticated and the endpoint is present on every default storefront. In the 2019 Magecart era, unpatched Magento SQLi and RCE were a primary route to planting card skimmers in checkout, and this class of bug — read the crypt key, forge admin, inject payment JavaScript — was exactly the pattern operators used at scale.

Why patch status is not enough

The endpoint and the vulnerable code path exist regardless of whether the store has ever used "recently viewed products." A store on 2.3.0 is exploitable even with that feature visually disabled.

How to check

  • Confirm the exact patch level: bin/magento --version, and cross-check against 2.1.18 / 2.2.9 / 2.3.2 — the NVD-authoritative fixed baseline for this CVE. (2.3.1 / 2.2.8 / 2.1.17 carried Magento's PRODSECBUG-2198 patch, but NVD still lists 2.3.1 as affected, so the higher line is the safe floor.)
  • Verify the fix is actually present in code rather than trusting the version string, since patched-by-composer and vendor-patched trees diverge:
Confirm the corrected condition assembly is in place
grep -n "_prepareQuotedSqlCondition" \
  vendor/magento/framework/DB/Adapter/Pdo/Mysql.php

# Fixed builds quote only the `to` value, then concatenate:
#   $query = $query . $this->_prepareQuotedSqlCondition(
#              $conditionKeyMap['to'], $to, $fieldName);
  • Review web-server and application logs for POST requests to catalog/product_frontend_action/synchronize containing ][from]/][to] with SQL keywords, UNION, SLEEP(, or stacked comment markers.

How to fix it

There is no configuration mitigation that fully closes this; the query builder must be corrected. Upgrade to a fixed release or apply the official patch.

Composer path — bring the framework to a fixed baseline
composer require magento/product-community-edition 2.3.2 \
  --no-update
composer update magento/framework --with-dependencies
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento cache:flush

If you cannot take a full minor upgrade, apply the isolated vendor patch for PRODSECBUG-2198 (distributed via the official security package / quality patches) rather than hand-editing vendor/:

Isolated patch, preferred over editing vendor by hand
composer require magento/quality-patches
bin/magento setup:upgrade
./vendor/bin/magento-patches status
./vendor/bin/magento-patches apply MDVA-<id>  # PRODSECBUG-2198 backport

After patching, treat the store as potentially compromised for the exposure window: rotate the crypt/key, invalidate all admin sessions, force admin password resets, and audit core_config_data and checkout templates for injected script. A read-only SQLi that leaks the crypt key does not "expire" when you patch — the stolen secret still decrypts saved sessions until it is rotated.

References & sources

Primary sources — advisories, vendor research, and the CVE record. Verify against these, not us.

  1. Charles Fol / Lexfo — Magento 2 Unauthenticated SQLi (Ambionics research)
  2. NVD — CVE-2019-7139 (CVSS 9.8, CWE-89)
  3. Magento security update (2.1.18, 2.2.9, 2.3.2)
  4. Snyk — CVE-2019-7139 (magento/community-edition)
  5. Vulhub — reproducible Magento 2.2 SQLi lab
CRITICAL CVES CVE-2015-1397 CVSS 6.5 scan.magekwik.com
critical Critical CVEs

Shoplift (CVE-2015-1397): the Magento 1 SQL injection that was weaponised in 24 hours

CVE-2015-1397 is the SQL injection at the centre of the Magento 1 "Shoplift" chain — an unauthenticated grid-export flaw that let attackers write admin accounts straight into the database. Patched by SUPEE-5344 in February 2015, it was mass-exploited within a day of disclosure. The mechanism, the indicators of compromise, and the fix.

6 min read

← All security posts