{"id":"GHSA-qxvg-h7q2-hcxh","summary":"motionEye: LFI → pass‑the‑hash admin → unsafe restore → unauth action exec (RCE)","details":"## Summary\nA multi‑stage chain in motionEye leads to remote code execution. The chain combines:\n\n1. **Arbitrary file read (LFI)** via the picture download endpoint for **local motion cameras** using absolute paths.\n2. **Pass‑the‑hash admin auth** due to accepting request signatures computed with password hashes.\n3. **Unsafe config restore** that extracts attacker‑controlled tarballs into `CONF_PATH`.\n4. **Unauthenticated action execution** via `/action/\u003cid\u003e/\u003caction\u003e`.\n\nIf the **normal user password is unset**, the chain becomes **unauthenticated RCE**. If a normal password exists, a **normal user** can still achieve **admin escalation and RCE**.\n\n---\n\n## Affected Code (motionEye repo)\n\n### 1) LFI (absolute path) — `picture/\u003cid\u003e/download`\n**Files:**\n- `motioneye/motioneye/handlers/picture.py` → `download()` (local motion camera branch)\n- `motioneye/motioneye/mediafiles.py` → `get_media_content()`\n\n**Issue:** `get_media_content()` only blocks `..` and then joins `target_dir` with `path`. Absolute paths (e.g. `/etc/hosts`) bypass the join and are read directly.\n\n### 2) Pass‑the‑hash admin auth\n**File:** `motioneye/motioneye/handlers/base.py` → `get_current_user()`\n\n**Issue:** The signature check allows signatures computed using the **admin password hash** (SHA1) as the key. If the hash is leaked (via LFI), admin access can be obtained without the plaintext password.\n\n### 3) Unsafe restore (tar extraction)\n**File:** `motioneye/motioneye/config.py` → `restore()`\n\n**Issue:** `tar zxC CONF_PATH` is used on user‑supplied data without sanitizing entries. A crafted tar can drop executable files into `CONF_PATH`.\n\n### 4) Unauthenticated action execution\n**File:** `motioneye/motioneye/handlers/action.py` → `post()`\n\n**Issue:** No authentication decorator is present. It executes `\u003caction\u003e_\u003ccamera_id\u003e` found in `CONF_PATH` with `subprocess.Popen`.\n\n---\n\n## Exploit Chain (Detailed)\n\n1. **Create or find a local motion camera id** (local motion cameras are required for the vulnerable LFI path).\n2. **LFI via picture download**:\n   - Request: `/picture/\u003cid\u003e/download/\u003cabsolute_path\u003e`\n   - Example: `/picture/1/download/%2Fetc%2Fhosts`\n   - Result: Arbitrary file read.\n3. **Read admin hash** from `/etc/motioneye/motion.conf`:\n   - Contains `@admin_password \u003cSHA1_HASH\u003e`.\n4. **Pass‑the‑hash admin**:\n   - Compute signature for `/config/restore?_username=admin` using the **hash** as key.\n   - Admin access is accepted with hash‑based signatures.\n5. **Restore malicious tar**:\n   - Upload a tar containing `lock_\u003cid\u003e` (or any action) as an executable.\n   - File is written into `CONF_PATH` by restore.\n6. **Trigger unauth action**:\n   - POST `/action/\u003cid\u003e/lock`\n   - The server executes the injected file.\n\n---\n\n## Proof of Execution (Observed Output)\nIn local testing, the injected action created a marker file:\n\n```\n/tmp/meye_rce_ok\n```\n\nVerification command:\n```\ndocker exec -it motioneye ls -la /tmp | grep meye_rce_ok\n```\nExample output:\n```\n-rw-r--r-- 1 root root 0 ... /tmp/meye_rce_ok\n```\n\n---\n\n## Preconditions / Requirements\n\n- At least **one local motion camera** exists (e.g., `netcam_url`, `videodevice`).\n- `picture/\u003cid\u003e/download` is reachable:\n  - **Unauth** if `@normal_password` is empty (default in some installs).\n  - **Auth required** if normal password is set (attacker needs normal creds).\n\n---\n\n## Impact\n- **Unauth RCE** (normal password unset).\n- **Authenticated RCE** (normal user → admin → RCE).\n- Arbitrary file read on server filesystem.\n- Full compromise of motionEye process account.\n\n---\n\n## Suggested Fixes\n1. **Block absolute paths** in `get_media_content()` and `get_media_path()`.\n2. **Remove hash‑based signature acceptance**; only accept signatures computed with plaintext passwords.\n3. **Harden restore**: reject absolute paths, `..`, symlinks, non‑regular files.\n4. **Require authentication** on `ActionHandler` (admin‑only).","modified":"2026-06-23T19:00:08.688867471Z","published":"2026-06-23T18:53:04Z","database_specific":{"severity":"CRITICAL","github_reviewed":true,"github_reviewed_at":"2026-06-23T18:53:04Z","nvd_published_at":null,"cwe_ids":["CWE-22","CWE-269","CWE-306","CWE-347","CWE-434"]},"references":[{"type":"WEB","url":"https://github.com/motioneye-project/motioneye/security/advisories/GHSA-qxvg-h7q2-hcxh"},{"type":"PACKAGE","url":"https://github.com/motioneye-project/motioneye"}],"affected":[{"package":{"name":"motioneye","ecosystem":"PyPI","purl":"pkg:pypi/motioneye"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0.44.0"}]}],"versions":["0.27","0.27.1","0.27.2","0.28","0.28.1","0.28.2","0.28.3","0.29","0.29.1","0.29rc1","0.29rc2","0.30","0.30rc1","0.30rc2","0.31","0.31.1","0.31.2","0.31.3","0.31.4","0.31.5","0.32","0.32.1","0.32.2","0.33","0.33.1","0.33.2","0.33.3","0.33.4","0.34","0.34.1","0.34rc1","0.35","0.35.1","0.35.2","0.35rc1","0.36","0.36.1","0.37","0.37.1","0.37rc1","0.38","0.38.1","0.39","0.39.1","0.39.2","0.39.3","0.40","0.40rc1","0.40rc2","0.40rc3","0.40rc4","0.40rc5","0.41","0.41rc1","0.42","0.42.1","0.43.1","0.43.1b1","0.43.1b2","0.43.1b3","0.43.1b4","0.43.1b5","0.44.0b1","0.44.0b2","0.44.0b3"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/06/GHSA-qxvg-h7q2-hcxh/GHSA-qxvg-h7q2-hcxh.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"}]}